OpenOffice-OODoc-2.125/0000755000175000017500000000000011416644377012632 5ustar jmgjmgOpenOffice-OODoc-2.125/build_date0000644000175000017500000000002311416644373014640 0ustar jmgjmg2010-07-12T18:55:23OpenOffice-OODoc-2.125/INSTALL0000644000175000017500000001610111347231631013646 0ustar jmgjmgOpenOffice::OODoc installation (2010-01-06) SYSTEM REQUIREMENTS Perl >= 5.8.0 Archive::Zip >= 1.18 XML::Twig >= 3.32 Time::Local >= 1.07 File::Temp >= 0.12 INSTALLATION FROM THE CPAN DISTRIBUTION Uncompress the distribution archive, enter the OpenOffice-OODoc-x.xxx directory and (as system administrator), type the following commands: perl Makefile.PL [options] make test make install For MSWin32, "make" can be replaced by "nmake". If the Microsoft NMAKE utility is not present in your environment, you can get it at http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe If Archive::Zip and XML::Twig are already installed, this CPAN installation works without a C compiler, because OpenOffice::OODoc is pure Perl. Otherwise, if these required modules are not installed and if you don't have a C development environment, you should use another distribution (such as the PPM one for ActivePerl, if available) instead, knowing that the CPAN Archive::Zip and XML::Twig distributions can indirectly bring a lot of C source code. You will be prompted for the local character set (default = utf8), an optional color map (default = none), the working directory path (default = current directory of each application), and the default file format (OOo 1.0 or ODF, default = ODF). The defaults are convenient is most situations; however the following explanations could prove useful. The default local character set is 'utf8', but it may be anyone of the character sets supported by the Encode Perl module. The standard ODF internal character set is always utf8 but the OpenOffice::OODoc module transparently allows the applications to deal with the text content as if it was natively in their local, possibly non-utf8 character set. The appropriate transcoding is automatically provided, according to the declared local character set. The role of the optional color map file is to allow the programmer to use symbolic names instead of RGB values for color attributes, knowing that OpenOffice::OODoc allows the applications to specify color parameters in some situation (characters, backgrounds, shapes, borders, and so on). Each line of this configuration file, if provided, should have the following structure: R G B name where 'R', 'G' and 'B' are integer values in the 0-255 range for red, green and blue, and 'name' is an arbitrary symbolic name for the given RGB combination. Example: 135 206 235 SkyBlue The line above in the color map file allows the application programmer to use "SkyBlue" as a replacement for the [135,206,235] list with a color definition function. Such a file may be created by the user or borrowed to the environment. For example, the standard RGB file that is available in a typical X-Window or Xorg environment may be used as is or customized (this file is often located at /etc/X11/rgb.txt on Unix-like platforms, and it may be downloaded on any non-Unix platform). The choice of the working directory may be a sensitive choice in constrained environments and/or for long-running processes, while it's generally not an issue in a typical office environment. Each time a document is created or updated, OpenOffice::OODoc generates intermediate files which are automatically deleted after use (some of them can remain in case of crash only). The default path is ".", meaning that these intermediate files will be created in the current directory of each application; if needed, it may be replaced by any absolute or relative path. In distributed environments, it's recommended to specify a location in a local filesystem for performance reasons. For historical reasons, OpenOffice::OODoc supports both the primary OpenOffice.org 1.0 file format (now deprecated) and the present standard Open Document Format (ODF). By default, ODF is the preferred format, and it's strongly recommended to let this configuration parameter unchanged. The choice of a preferred format doesn't prevent the applications to process documents in the other format; the format that is declared as "preferred" will just be selected to create any new document, but OpenOffice::OODoc will not change the format of an existing document. Be careful: knowing that the legacy OpenOffice.org 1.0 format is disappearing, it will not necessarily supported by future versions of OpenOffice::OODoc. OpenOffice::OODoc currently supports ODF 1.0 to 1.2; however, it doesn't include any validating feature, so the users are not prevented from using its API to insert custom, non-standard XML constructs in the documents. - the preferred file format, to be used when you create a new document from scratch (answer "1" for OpenOffice.org, "2" for OASIS OpenDocument Format, default is "2"). The interactivity can be avoided by the --noprompt option. The parameters can be provided at the command line with (respectively) the --encoding , --colormap , --workdir and --format options. Example: perl Makefile.PL --noprompt --workdir "C:\Temp" --encoding "cp1252" The full customization step can be avoided with the --noconfig option. If this option is used, all the default values are installed. These options define installation-level default values only; each of these values can be overridden by the applications (thanks, for example, to the odfLocalEncoding(), odfWorkingDirectory() and odfLoadColorMap() functions). The installation-level options are stored in a XML file (OODoc/config.xml) below the installation directory. This file can be manually edited at any time after the installation in order to change any parameter. A variable $OpenOffice::OODoc::INSTALLATION_DATE is available for the applications; it contains the installation date in ISO-8601 format. If the customization has been skipped (due to the --noconfig option), this variable contains the packaging date of the distribution. Caution, this value is significant if the installation has been done from the original CPAN distribution only. The date of the original CPAN package is provided by the variable $OpenOffice::OODoc::BUILD_DATE. If the installation is successful, the test procedure generates a document, writes some content in it, and checks the result. This document is named 'odftest.odt' or 'ootest.sxw' (according to your default file format) and resides in the working directory of the installation. You can later check this document with a compatible text processor or viewer. A Perl executable script, oodoc_version, is provided in the package. After a successful installation using the CPAN distribution, this script displays the version number, the package build date, and the installation path. A more sophisticated script, oodoc_test, is provided as an executable example; this script generates a document which may be checked using an ODF-compatible text processor. Be careful, while both oodoc_version and oodoc_test are provided in the original CPAN distribution, they are not necessarily available in any derived OpenOffice::OODoc package. OpenOffice-OODoc-2.125/META.yml0000644000175000017500000000125611416644377014107 0ustar jmgjmg--- #YAML:1.0 name: OpenOffice-OODoc version: 2.125 abstract: The Perl Open OpenDocument Connector author: - Jean-Marie Gouarne license: LGPL distribution_type: module configure_requires: ExtUtils::MakeMaker: 0 build_requires: ExtUtils::MakeMaker: 0 requires: Archive::Zip: 1.18 File::Find: 1.01 File::Temp: 0.12 IO::File: 1.14 Time::Local: 1.07 XML::Twig: 3.32 no_index: directory: - t - inc generated_by: ExtUtils::MakeMaker version 6.56 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 OpenOffice-OODoc-2.125/Makefile.PL0000644000175000017500000001143711415134663014601 0ustar jmgjmg#------------------------------------------------------ # OpenOffice::OODoc packaging & installation 2010-07-07 #------------------------------------------------------ use 5.008; use strict; use Getopt::Long; use Encode; use ExtUtils::MakeMaker; #------------------------------------------------------ GetOptions ( 'config!' => \(my $config = 1), 'prompt!' => \(my $prompt = 1), 'encoding=s' => \(my $encoding = 'utf8'), 'colormap=s' => \(my $colormap = ''), 'workdir=s' => \(my $workdir = '.'), 'format=s' => \(my $format = 2), 'build' => \(my $build = undef) ); #------------------------------------------------------ print "OpenOffice::OODoc Installation\n"; if ($config) { my $config_file = 'OODoc/config.xml'; print "\nNow you will be prompted for some configuration options.\n" . "These options are intended to define default values only.\n" . "Each option may be overridden by the applications.\n" . "You can change these default options later; to do so, you\n" . "can either replay this installation procedure or manually\n" . "edit the /$config_file file.\n" . "(See INSTALL for details)\n" if ($prompt); while ($prompt) { my $answer; print "\nPresent configuration:\n"; print "- Your local character set is now [$encoding]\n"; unless (Encode::find_encoding($encoding)) { warn "\tWARNING: Unsupported character set.\n"; } unless ($colormap) { print "- There is no colour map file\n"; } else { print "- Your colour map file is now [$colormap]\n"; unless (-f $colormap) { warn "\tWARNING: This file doesn't exist.\n"; } } print "- Your working directory for temporary files is now [$workdir]\n"; unless (-d $workdir) { warn "\tWARNING: This directory doesn't exist.\n"; } print "- Your preferred file format is [$format] (OOo=1 ODF=2).\n"; $answer = lc (prompt("Is that OK (y/n) ?", "y")); $prompt = '' unless $answer ne "y"; last unless $prompt; $encoding = prompt ( "What is your preferred local character set ?", $encoding ); $colormap = prompt ( "What is the full path of your RGB colour map file (optional) ?", $colormap ); $workdir = prompt ( "What is your working directory for temporary files ?", $workdir ); $format = prompt ( "What is your preferred format for new documents (OOo=1, ODF=2) ?", $format ); unless ($format eq "1" or $format eq "2") { warn "\tWARNING: Unknown format $format\n"; $format = 2; } $encoding = '' unless $encoding gt ' '; $colormap = '' unless $colormap gt ' '; $workdir = '' unless $workdir gt ' '; } Encode::from_to($workdir, $encoding, 'utf8'); #------------------------------------------------------ my @lt = localtime(); my $current_date = sprintf ( "%04d-%02d-%02dT%02d:%02d:%02d", $lt[5] + 1900, $lt[4] + 1, $lt[3], $lt[2], $lt[1], $lt[0] ); my $install_date = undef; my $build_date = undef; if ($build) { open TS, ">", "build_date"; print TS $current_date; close TS; $build_date = $current_date; $install_date = "N/A"; } else { open TS, "<", "build_date"; $build_date = ; close TS; $install_date = $current_date; } #------------------------------------------------------ open CF, ">", $config_file; print CF '' . "\n"; print CF "\n"; print CF "\tOpenOffice::OODoc local configuration file\n"; print CF "\t\n"; print CF "\t\t$format\n"; print CF "\t\t$encoding\n"; print CF "\t\t$workdir\n"; print CF "\t\t$colormap\n"; print CF "\t\t$build_date\n"; print CF "\t\t$install_date\n"; print CF "\t\n"; print CF ""; close CF; } #------------------------------------------------------ WriteMakefile ( 'NAME' => 'OpenOffice::OODoc', 'VERSION_FROM' => 'OODoc.pm', 'ABSTRACT_FROM' => 'OODoc.pod', 'AUTHOR' => 'Jean-Marie Gouarne ', 'LICENSE' => 'LGPL', 'PREREQ_PM' => { 'XML::Twig' => '3.32', 'Archive::Zip' => '1.18', 'File::Temp' => '0.12', 'File::Find' => '1.01', 'Time::Local' => '1.07', 'IO::File' => '1.14' }, 'EXE_FILES' => [ 'oodoc_version', 'examples/odfhighlight', 'examples/odfextract', 'examples/odffilesearch', 'examples/text2odf', 'examples/text2table', 'examples/odf2pod', 'examples/odfbuild', 'examples/odfmetadoc', 'examples/odffindbasic', 'examples/odfsearch', 'examples/odf_set_fields', 'examples/odf_set_title', 'examples/oodoc_test' ] ); #------------------------------------------------------ OpenOffice-OODoc-2.125/README0000644000175000017500000000372411330007776013507 0ustar jmgjmg------------------------------------------------------------------------ OpenOffice::OODoc - The Open OpenDocument Connector (http://search.cpan.org/dist/OpenOffice-OODoc) ------------------------------------------------------------------------- For a introductory description of the purposes and capabilities of OpenOffice::OODoc, please read the OpenOffice::OODoc::Intro manual page. This page in contained in the OODoc/Intro.pod file of the CPAN distribution. It can be read on line using the following link: http://search.cpan.org/dist/OpenOffice-OODoc/OODoc/Intro.pod There is an alternative intro for french-reading users. It's available in ODF (http://jean.marie.gouarne.online.fr/doc/oodoc_guide.odt) or PDF format (http://jean.marie.gouarne.online.fr/doc/oodoc_guide.pdf). See INSTALL for system requirements and installation instructions for the CPAN original distribution. The present version supports the OASIS OpenDocument Format (ODF). It works with ODF 1.0 to 1.2, knowing that its present features are not directly impacted by the changes between the successive versions of the ODF standard. The legacy OpenOffice.org 1.0 format is still supported, too; however, the use of this deprecated format is not recommended for new documents. In addition, advanced users can rely on OpenOffice::OODoc to process any kind of flat or zipped XML files that don't comply with the ODF standard, so they can combine ODF documents with non-ODF XML data through a single application programming interface. ------------------------------------------------------------------------- This software is free software. For more information about the licence, see LICENCE. Comments are welcome. They should be posted through the public forum (http://www.cpanforum.com/dist/OpenOffice-OODoc). Bug reports should be registered through the CPAN Request Tracker (http://rt.cpan.org/Public/Dist/Display.html?Name=OpenOffice-OODoc) ------------------------------------------------------------------------- OpenOffice-OODoc-2.125/OODoc.pm0000644000175000017500000001301511415435332014117 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : OODoc.pm 2.125 2010-07-08 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2010 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- use OpenOffice::OODoc::File 2.203; use OpenOffice::OODoc::Meta 2.017; use OpenOffice::OODoc::Document 2.023; use OpenOffice::OODoc::Manifest 2.007; #----------------------------------------------------------------------------- package OpenOffice::OODoc; use 5.008_000; use strict; our $VERSION = '2.125'; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw ( ooXPath ooText ooMeta ooManifest ooImage ooStyles odfXPath odfText odfMeta odfManifest odfImage odfStyles odfConnector odfDocument ooDocument odfPackage odfContainer ooFile odfLocalEncoding localEncoding ooLocalEncoding odfEncodeText odfDecodeText ooEncodeText ooDecodeText ooLocaltime ooTimelocal odfLocaltime odfTimelocal odfTemplatePath ooTemplatePath odfWorkingDirectory workingDirectory ooWorkingDirectory odfReadConfig readConfig ooReadConfig ); our $INSTALLATION_PATH; #----------------------------------------------------------------------------- # config loader sub odfReadConfig { my $filename = shift; unless ($filename) { $filename = $INSTALLATION_PATH . '/config.xml' if $INSTALLATION_PATH; } unless ($filename) { warn "[" . __PACKAGE__ . "::odfReadConfig] " . "Missing configuration file\n"; return undef; } my $config = XML::Twig->new->safe_parsefile($filename); unless ($config) { warn "[" . __PACKAGE__ . "::odfReadConfig] " . "Syntax error in configuration file $filename\n"; return undef; } my $root = $config->get_xpath('//OpenOffice-OODoc', 0); unless ($root && $root->isElementNode) { return undef; } foreach my $node ($root->getChildNodes) { next unless $node->isElementNode; my $name = $node->getName; $name =~ s/-/::/g; my $varname = 'OpenOffice::OODoc::' . $name; no strict; $$varname = $node->string_value; $$varname = odfDecodeText($$varname); use strict; } OpenOffice::OODoc::Styles::ooLoadColorMap(); return 1; } #----------------------------------------------------------------------------- # accessor for local character set control sub odfLocalEncoding { my $newcharset = shift; if ($newcharset) { if (Encode::find_encoding($newcharset)) { $OpenOffice::OODoc::XPath::LOCAL_CHARSET = $newcharset; } else { warn "[" . __PACKAGE__ . "::odfLocalEncoding] " . "Unsupported encoding\n"; } } return $OpenOffice::OODoc::XPath::LOCAL_CHARSET; } #----------------------------------------------------------------------------- # accessor for default XML templates for document creation sub odfTemplatePath { return OpenOffice::OODoc::File::templatePath(@_); } #----------------------------------------------------------------------------- # accessor for default working directory control sub odfWorkingDirectory { my $path = shift; $OpenOffice::OODoc::File::WORKING_DIRECTORY = $path if defined $path; OpenOffice::OODoc::File::checkWorkingDirectory ( $OpenOffice::OODoc::File::WORKING_DIRECTORY ); return $OpenOffice::OODoc::File::WORKING_DIRECTORY; } #----------------------------------------------------------------------------- # shortcuts for low-level local/utf8 code conversion sub odfEncodeText { return OpenOffice::OODoc::XPath::encode_text(@_); } sub odfDecodeText { return OpenOffice::OODoc::XPath::decode_text(@_); } #----------------------------------------------------------------------------- # constructors sub odfDocument { return OpenOffice::OODoc::Document->new(@_); } sub odfContainer { return OpenOffice::OODoc::File->new(@_); } sub odfXPath { return OpenOffice::OODoc::XPath->new(@_); } sub odfText { return OpenOffice::OODoc::Text->new(@_); } sub odfStyles { return OpenOffice::OODoc::Styles->new(@_); } sub odfImage { return OpenOffice::OODoc::Image->new(@_); } sub odfMeta { return OpenOffice::OODoc::Meta->new(@_); } sub odfManifest { return OpenOffice::OODoc::Manifest->new(@_); } #----------------------------------------------------------------------------- # initialization BEGIN { *ooDocument = *odfDocument; *odfConnector = *odfDocument; *odfFile = *odfContainer; *odfPackage = *odfContainer; *ooFile = *odfContainer; *ooXPath = *odfXPath; *ooText = *odfText; *ooStyles = *odfStyles; *ooImage = *odfImage; *ooMeta = *odfMeta; *ooManifest = *odfManifest; *localEncoding = *odfLocalEncoding; *workingDirectory = *odfWorkingDirectory; *readConfig = *odfReadConfig; *ooLocalEncoding = *odfLocalEncoding; *ooWorkingDirectory = *odfWorkingDirectory; *ooReadConfig = *odfReadConfig; *ooEncodeText = *odfEncodeText; *ooDecodeText = *odfDecodeText; *ooTemplatePath = *odfTemplatePath; *odfLocaltime = *OpenOffice::OODoc::XPath::odfLocaltime; *odfTimelocal = *OpenOffice::OODoc::XPath::odfTimelocal; *ooLocaltime = *odfLocaltime; *ooTimelocal = *odfTimelocal; my $module_path = $INC{"OpenOffice/OODoc.pm"}; $module_path =~ s/\.pm$//; $INSTALLATION_PATH = $module_path; odfReadConfig() if ( -e "$INSTALLATION_PATH/config.xml" ); } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/examples/0000755000175000017500000000000011416644377014450 5ustar jmgjmgOpenOffice-OODoc-2.125/examples/odffilesearch0000644000175000017500000001611111007414066017153 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id: odffilesearch 0.007 2008-05-04$ #----------------------------------------------------------------------------- =head1 NAME odffilesearch - File selection by keywords =head1 SYNOPSIS odffilesearch -R "D:\Documents\*.odt" openoffice desktop XML produces the list of the ODF Text documents present in the given directory and its subdirectories, and containing the words "openoffice", "desktop" AND "XML" odffilesearch -command "rm -f %f" "*.ods *.odt" lost dismiss cancel executes the "rm -f filename" (i.e. deletes the file in a Unix system) for each ODT or ODS file present in the current directory and containing the words "lost", "dismiss" AND "cancel" =head1 USAGE odffilesearch [-options] =head1 DESCRIPTION This utility allows the user to retrieve a list of files matching a given set of keywords or regular expressions. A file is selected when it contains, in its text and/or in its metadata (title, subject, keywords or description), all the given search strings. The selected files are echoed to the standard output (one file per line), so this utility can be used as a filter piping its results to another program. Alternatively, a given shell command can be launched by the script each time a file matches, allowing on-the-fly processing of the selected documents. The files filter may content one or more space-separated paths. Each path may content jokers. So it's possible to explore several directories and/or several filename patterns. All the arguments after the file filter are processed as search criteria. =head1 OPTIONS -R -recursive include the subdirectories of each given search directory -verbose -trace -debug echo some processing comments -warnings activate the warning messages of the OpenOffice::OODoc API -log like -verbose, but then messages are sent to the given file and don't pollute the standard output -result -output send the list of matching files to the given file and not to the standard output -criteria get search criteria from a file (one per line); the loaded search keywords may be combined with additional criteria passed with the command line, if any. -command -exec execute a shell command for each matching file ; if the command string contains "%f", this substring is replaced with the name of the selected file ; if this option is provided, the selection list is not echoed to the standard output ; if -verbose is on, the value returned by the command is echoed -encoding selects the user's character set ; this option is mandatory if one or more search criteria contain characters not belonging to the default character set =cut #============================================================================= use OpenOffice::OODoc 2.101; use Getopt::Long; our $VERSION = 0.007; #============================================================================= my $recursive = undef; my $verbose = undef; my $warnings = undef; my $command = undef; my $result = undef; my $log = undef; my $list = undef; my $character_set = undef; my $RESULT = *STDOUT; my $LOG = *STDOUT; GetOptions ( 'R|recursive' => \$recursive, 'verbose|trace|debug' => \$verbose, 'warnings' => \$warnings, 'log=s' => \$log, 'result|output=s' => \$result, 'command|exec=s' => \$command, 'criteria=s' => \$list, 'encoding=s' => \$character_set ); #============================================================================= my @keywords = (); my $count = 0; #============================================================================= sub horodate { my @d = localtime(); return sprintf ( "[%02d/%02d/%04d %02d:%02d:%02d] ", $d[3], $d[4] + 1, $d[5] + 1900, $d[2], $d[1], $d[0] ); } sub message { my $text = shift; return unless ($verbose); print $LOG horodate() . "$text\n"; print $LOG "\t$_\n" for @_; } #----------------------------------------------------------------------------- sub matching_file { my $file = shift; my @words = @_; my $n = scalar @words; my $text = ""; my $oof = ooFile($file); unless ($oof) { message "$file doesn't look like an ODF file"; return undef; } my $meta = odfMeta(container => $oof) or message "$file doesn't contain metadata"; if ($meta) { my $title = $meta->title; if ($title) { message "Title: \"$title\""; $text .= $title; } else { message "Title: "; } $text .= ($meta->keywords || ""); $text .= ($meta->subject || ""); $text .= ($meta->description || ""); $meta->dispose; } my $content = odfText(container => $oof) or message "$file doesn't have a regular content"; if ($content) { $text .= ($content->getTextContent || ""); $content->dispose; } return undef unless $text; while (@words) { my $word = shift @words or next; return undef unless $text =~ /$word/i; } return 1; } #----------------------------------------------------------------------------- sub file_selection { my @list = @_; my $number = scalar @list; message "$number file(s) in the search list"; FILE: foreach my $file (@list) { unless (-r $file) { message "$file : unreadable"; next FILE; } if (-l $file) { message "$file : symbolic link, ignored"; next FILE; } if ((-d $file) && $recursive) { message "Searching in $file"; file_selection(glob("$file/*")); next FILE; } unless (-s $file) { message "$file : empty"; next FILE; } unless (-f $file) { message "$file is not a regular file"; next FILE; } message "Processing $file"; if (matching_file($file, @keywords)) { message "OK! $file matches all the criteria"; if ($command) { my $cmd = $command; $cmd =~ s/\%f/$file/g; message "Executing command: $cmd"; my $r = system $cmd; message "Command result is $r"; } else { print $RESULT "$file\n"; } $count++; } else { message "file $file doesn't match"; } } } #============================================================================= # main program if ($result) { open RESULT, ">", $result or die "output file $result is unwritable\n"; $RESULT = *RESULT; } if ($log) { open OUTPUT, ">>", $log or die "log file $log is unwritable\n"; $LOG = *OUTPUT; $verbose = 1; } if ($list) { message "Loading a keyword list from $list"; my $m; open LIST, "<", $list or warn "file $list is unreadable\n"; while ($m = ) { chomp $m; push @keywords, $m; } close LIST; } localEncoding($character_set) if $character_set; die "Usage: odffilesearch [-options] [keywords]\n" unless $ARGV[0]; message "Starting the search..."; my $filter = shift @ARGV; push @keywords, @ARGV; die "Empty keword list.\n" unless @keywords; message "Keyword list:", @keywords; unless ($warnings) { $SIG{'__WARN__'} = sub {}; } file_selection(glob($filter)); message "Finished - $count file(s) selected"; exit; #============================================================================= OpenOffice-OODoc-2.125/examples/odfsearch0000644000175000017500000000622611322554622016324 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odfsearch 0.3 2010-01-11 JMG$ #----------------------------------------------------------------------------- =head1 NAME odfsearch - Text selection and copy from one document to another using callback =head1 SYNOPSIS This sample script extracts the content of every text element (paragraph, header, list item) in a source document matching a given pattern (string or regex), and appends it as a new paragraph to a target document. The target document must exist. The target document must exist (it can be an empty document or a template). usage : odfsearch target_file source_file "search_string" =cut use OpenOffice::OODoc 2.101; our $count = 0; #----------------------------------------------------------------------------- # callback function executed each time a matching element is found while # the selectElementsByContent method is running. # the last argument is the selected element; it's preceded by # application-specific arguments sub append_text { my ($dst, $stylename, $element) = @_; $count++; # convert the given element to flat text # we use getText as a class method; here we don't need/know # the context of the source document my $text = OpenOffice::OODoc::Text->getText($element); # append as a new paragraph to target document $dst->appendParagraph(text => $text, style => $stylename); return undef; } #----------------------------------------------------------------------------- # main program # get the command line arguments my $target_file = $ARGV[0]; my $source_file = $ARGV[1]; my $search_string = $ARGV[2]; my $stylename = $ARGV[3] || 'Text body'; die "Usage : odfsearch target_file source_file search_string\n" unless ($target_file && $source_file && $search_string); # create the 2 Document instances print "Opening the target file $target_file...\n"; my $target_doc = odfDocument(file => $target_file) or die "$target_file is not available\n"; print "Opening the source file $source_file...\n"; my $source_doc = odfDocument(file => $source_file) or die "$source_file is not available\n"; # the main processing takes place here : # the search method invocation, with search string and reference to # the procedure to be executed to process each selected element. # The selected element is automatically passed as an argument to the # callback, following explicit (optional) arguments provided by # the application (here $source_doc, $target_doc, $stylename) print "Selecting the content...\n"; $source_doc->selectElementsByContent ( $search_string, # filter expression \&append_text, # callback function reference $target_doc, $stylename # callback function arguments ); # append a conclusion my $report = "$count text elements from $source_file " . "have been selected, converted in paragraphs " . "and appended to $target_file. Good reading."; $target_doc->appendParagraph(text => $report, style => $stylename); # save the changes and leave print "Saving the target document...\n"; $target_doc->save; print "Job complete\n"; exit; #----------------------------------------------------------------------------- OpenOffice-OODoc-2.125/examples/oodoc_test0000644000175000017500000001475211335034053016526 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # OpenOffice::OODoc test 2010-02-11 #----------------------------------------------------------------------------- =head1 NAME oodoc_test - OpenOffice::OODoc test document generation =head1 DESCRIPTION This utility checks the current OpenOffice::OODoc installation and generates a test document from scratch. The generated document can then be found in the current directory, and its name is "odftest.odt". =cut use strict; use OpenOffice::OODoc; #----------------------------------------------------------------------------- sub display_date { return localtime(odfTimelocal(shift)); } #----------------------------------------------------------------------------- my $version = $OpenOffice::OODoc::VERSION; my $pkgdate = display_date $OpenOffice::OODoc::BUILD_DATE; my $instpath = $OpenOffice::OODoc::INSTALLATION_PATH; print "OpenOffice::OODoc installation test\n Version\t\t$version\n" . " Build date\t\t$pkgdate\n" . " Installation path\t$instpath\n"; my $generator = "OpenOffice::OODoc " . $OpenOffice::OODoc::VERSION . " installation test"; my $testfile = $ARGV[0] || ( $OpenOffice::OODoc::File::DEFAULT_OFFICE_FORMAT == 2 ? "odftest.odt" : "ootest.sxw" ); my $class = "text"; my $image_file = "$instpath/data/image.png"; my $image_size = "60mm, 82mm"; my $test_date = odfLocaltime(); print "Generating $testfile file\n"; # Creating an empty new ODF file with the default template # unlink $testfile; my $archive = odfContainer($testfile, create => 'text'); unless ($archive) { die "# Unable to create the test file\n"; } #----------------------------------------------------------------------------- my $notice = "This document has been generated by the OpenOffice::OODoc " . "installation test. If you can read this paragraph in blue letters with " . "a yellow background, if you can see a centered image at the top of the " . "page, and if the informations below make sense, " . "your installation is probably OK."; my $title = "OpenOffice::OODoc test document"; my $description = "Generated by $generator"; # Opening the content using OpenOffice::OODoc::Document my $doc = odfConnector ( container => $archive, part => 'content', readable_XML => 'true' ) or die "# Unable to find a regular document content\n"; my $styles = odfConnector ( container => $archive, part => 'styles', readable_XML => 'true' ) or die "# Unable to get the styles\n"; # Creating a graphic style $styles->createImageStyle ( 'Centered Image', properties => { 'horizontal-pos' => 'center', 'vertical-pos' => 'from-top', 'wrap' => 'none', 'fo:margin-bottom' => '1cm' } ); # Inserting an image in the document $doc->createImageElement ( 'Logo', style => 'Centered Image', page => 1, size => $image_size, import => $image_file ); # Appending a page footer $styles->createStyle ( 'Centered Paragraph', family => 'paragraph', parent => 'Standard', properties => { 'fo:text-align' => 'center' } ); $styles->styleProperties ( 'Centered Paragraph', area => 'text', 'fo:font-size' => '60%' ); $styles->masterPageFooter ( 'Standard', $styles->createParagraph ( "Created by OpenOffice::OODoc\n" . localtime(), 'Centered Paragraph' ) ); # Appending a level 1 heading $doc->appendHeading ( text => "Congratulations !", level => "1", style => "Heading_20_1" ); # Creating a coloured paragraph style (blue foreground, yellow background) $styles->createStyle ( "Colour", family => 'paragraph', parent => 'Standard', properties => { -area => 'paragraph', 'fo:color' => odfColor(0,0,128), 'fo:background-color' => odfColor("yellow"), 'fo:text-align' => 'justify' } ); if ($doc->isOpenDocument) { $styles->styleProperties ("Colour", -area => 'text', 'fo:color' => '#000080'); } # Appending another paragraph using the new style my $pg = $doc->appendParagraph(text => $notice, style => "Colour" ); # Appending another level 1 heading $doc->appendHeading ( text => "Your environment", level => 1, style => "Heading_20_1" ); # Appending a table showing some environment details my $table = $doc->appendTable("Environment", 6, 2); $doc->cellValue($table, "A1", "Platform"); $doc->cellValue($table, "B1", $^O); $doc->cellValue($table, "A2", "Perl version"); $doc->cellValue($table, "B2", $]); $doc->cellValue($table, "A3", "Archive::Zip version"); $doc->cellValue($table, "B3", $Archive::Zip::VERSION); $doc->cellValue($table, "A4", "XML::Twig version"); $doc->cellValue($table, "B4", $XML::Twig::VERSION); $doc->cellValue($table, "A5", "OpenOffice::OODoc version"); $doc->cellValue($table, "B5", $OpenOffice::OODoc::VERSION); $doc->cellValue($table, "A6", "OpenOffice::OODoc build"); $doc->cellValueType($table, "B6", 'date'); $doc->cellValue($table, "B6", $OpenOffice::OODoc::BUILD_DATE); # Appending another level 1 heading $doc->appendHeading ( text => "Your installation choices", level => 1, style => "Heading_20_1" ); # Appending a table with the installation parameters my $office_format = $OpenOffice::OODoc::File::DEFAULT_OFFICE_FORMAT == 2 ? "OASIS Open Document" : "OpenOffice.org 1.0"; my $color_map = $OpenOffice::OODoc::Styles::COLORMAP || ""; $table = $doc->appendTable("Choices", 4, 2); $doc->cellValue($table, "A1", "Local character set"); $doc->cellValue($table, "B1", $OpenOffice::OODoc::XPath::LOCAL_CHARSET); $doc->cellValue($table, "A2", "Working directory"); $doc->cellValue($table, "B2", $OpenOffice::OODoc::File::WORKING_DIRECTORY); $doc->cellValue($table, "A3", "RGB color map"); $doc->cellValue($table, "B3", $color_map); $doc->cellValue($table, "A4", "Default office document format"); $doc->cellValue($table, "B4", $office_format); # Opening the metadata of the document my $meta = odfMeta(container => $archive, readable_XML => 'on') or die "# Unable to find regular metadata\n"; # Writing some metadata elements $meta->title($title); $meta->creator($ENV{'USER'}); $meta->initial_creator($ENV{'USER'}); $meta->description($description); $meta->generator($generator); $meta->creation_date($test_date); $meta->date($test_date); $meta->removeUserProperties(); $meta->setUserProperty("Application", type => 'string', value => "oodoc_test"); $meta->setUserProperty("Test date", type => 'date', value => $test_date); # Saving the $testfile file print "Saving $testfile file\n"; $archive->save; $doc->dispose; $styles->dispose; $meta->dispose; exit 0; OpenOffice-OODoc-2.125/examples/odfbuild0000644000175000017500000001404611010262001016132 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odfbuild 0.2 2008-05-04 JMG$ #----------------------------------------------------------------------------- =head1 NAME odfbuild - OpenDocument file creation utility =head1 SYNOPSIS odfbuild filename.odt odfbuild filename.odt --title "My Document" --subject "Test" odfbuild filename.ods --class spreadsheet --source "data.csv" --tablesize "8x16" cat data.txt | odfbuild filename.odt - =head1 OPTIONS --class Document class (text, spreadsheet, drawing, presentation) Default: text --opendocument (no value). If this option is on, the document will be in OpenDocument format. Without this option, the format will be selected according to the general configuration of the OpenOffice::OODoc installation. --creator The author of the document. Default: the current user's login name. --date Creation date. Default is current local time. If provided, must be in ISO-8601 format (YYYY-MM-DDTHH:MM:SS) --description The description (abstract) of the document. Default: none. --force (no value). If this option is on, any existing file with the same path as the target file will be replaced. Without this option, the program will fail if the target exists. --generator Software signature to be stored in the file (not visible for the end user). Default: "Genicorp OpenOffice::OODoc " --keywords A list of comma-separated keywords. Default: none. --source A text file, to be used as the content of the document. If the document class is 'text', each line is loaded as a new paragraph with the standard style. If the document class is 'spreadsheet', the file is processed as CSV data and loaded in one sheet. If the document class is neither 'text' nor 'spreadsheet', the file is not processed. If source = '-', or if a '-' argument is provided, the data file is read through the standard input. --subject The subject of the document. Default: none. --tablename The name of the sheet to be created if the document class is 'spreadsheet' and if a data file is provided. Default: the name of the data file, or "Unnamed Sheet" if the data is read from the standard input. --tablesize The size of the sheet to be created if the document class is 'spreadsheet' and if a data file is provided, in 'HxW' format where H is the number of lines and W the number of columns. Default: '16x8' --title The title of the document. Default: "Untitled". --readable_XML (no value). For debugging only. If this option is on, the XML content of the target file is indented, in order to be later edited. =cut #---------------------------------------------------------------------------------------- use OpenOffice::OODoc 2.101; use Getopt::Long; my $INPUT = undef; my $input = undef; my $generator = 'OpenOffice::OODoc ' . $OpenOffice::OODoc::VERSION; my $title = "Generated document"; my $description = "This file has been created with $generator"; my $targetfile = $ARGV[0] or die "Usage: oobuild [--options]\n"; GetOptions ( '' => \(my $stdin = undef), 'class=s' => \(my $class = 'text'), 'date=s' => \(my $date = odfLocaltime), 'generator=s' => \$generator, 'title=s' => \$title, 'subject=s' => \(my $subject = ''), 'description=s' => \$description, 'keywords=s' => \(my $keywords = ''), 'creator=s' => \(my $creator = scalar getpwuid($<)), 'source=s' => \(my $source = undef), 'tablesize=s' => \(my $tablesize = '16x8'), 'tablename=s' => \(my $tablename = undef), 'force' => \(my $force = undef), 'opendocument' => \(my $odf = undef), 'readable_XML' => \(my $rxml = undef) ); if ( -e $targetfile && ! defined $force) { die "File $targetfile exists. I don't create it.\n" . "Use --force to replace it.\n"; } my $odf_flag = $odf ? 'on' : undef; my $rxml_flag = $rxml ? 'on' : undef; my $archive = odfContainer ( $targetfile, create => $class, opendocument => $odf_flag ) or die "File creation failure\n"; #---------------------------------------------------------------------------------------- my $meta = odfMeta(container => $archive, readable_XML => $rxml_flag); $meta->creation_date($date); $meta->date($date); $meta->generator($generator); $meta->initial_creator($creator); $meta->creator($creator); $meta->title($title); $meta->subject($subject); $meta->description($description); $meta->keywords(split ',', $keywords) if $keywords; #---------------------------------------------------------------------------------------- if ($stdin || $source) { my $INPUT = undef; if ($stdin || ($source eq '-')) { $INPUT = *STDIN; $source = '-'; } else { if ( -e $source && -r $source ) { open(SOURCE, "<", $source); $INPUT = *SOURCE; } } my $content = odfDocument (container => $archive, readable_XML => $rxml_flag); if ($class eq 'text') { my $first_para = $content->getParagraph(0); while (my $para = <$INPUT>) { $content->appendParagraph(text => $para); } $content->removeElement($first_para) if $first_para; } elsif ($class eq 'spreadsheet') { my ($cols, $lns) = split 'x', $tablesize; unless ($tablename) { if ($source gt '-') { $tablename = $source; } else { $tablename = 'Unnamed Sheet'; } } my $first_sheet = $content->getTable(0); my $sheet = $content->appendTable($tablename, $cols, $lns); $content->removeElement($first_sheet) if $first_sheet; ROW: for (my $i = 0; my $record = <$INPUT>; $i++) { last ROW unless $record; chomp $record; my @data = split ';', $record; my $row = $content->getTableRow($sheet, $i); CELL: for (my $j = 0; my $value = shift @data; $j++) { last CELL unless defined $value; $content->cellValue($row, $j, $value); } } } else { warn "Source text loading not allowed for this class\n"; } close $INPUT unless ($source eq '-'); } #---------------------------------------------------------------------------------------- $archive->save; exit; #---------------------------------------------------------------------------------------- OpenOffice-OODoc-2.125/examples/odffindbasic0000644000175000017500000000626511007414004016771 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odffindbasic 0.2 2008-05-04 JMG$ #----------------------------------------------------------------------------- =head1 NAME odffindbasic - Basic macro removal from OpenOffice.org files =head1 USAGE odffindbasic [options] [] =head1 DESCRIPTION A simple command that allows the user to detect, export or remove the Basic modules from a regular OpenOffice.org file. Without option, the program displays the number of Basic modules found in the file. The 2nd filename is used in combination with the --delete option. =head1 OPTIONS --nocount Prevents the program from displaying the number of Basic modules found. --delete -d If this option is set, the Basic modules are physically deleted. The file manifest is updated accordingly. However, the document content remains unchanged, even if it contains some references to the deleted macros. The code is removed (so the macros are no longer executable). If a target file name is provided as a 2nd argument, the changes are saved in it and the source file remains unchanged. --export -e Exports the macros. Every Basic module is extracted and converted to a flat Basic source file in the current directory. The name of each created file is constructed according to the corresponding path in the ODF file, and its suffix is "bas". --list -l The Basic modules are listed through the standard output. --verbose Some information messages are printed. =cut use OpenOffice::OODoc 2.101; use Getopt::Long; my $show_count = 1; GetOptions ( 'nocount' => sub { $show_count = 0; }, 'list' => \(my $list = undef), 'delete' => \(my $delete = undef), 'export' => \(my $export = undef), 'verbose' => \(my $verbose = undef) ); exit unless $ARGV[0]; my $input = $ARGV[0] or die "Usage: oofindbasic []\n"; my $output = $ARGV[1] || $input; print "Loading $input\n" if $verbose; my $archive = odfFile($input) or die "Unavailable file\n"; my $manifest = undef; if ($delete) { print "Extracting the manifest\n" if $verbose; $manifest = odfManifest(file => $archive); } my $deleted = 0; my $count = 0; foreach my $m (@{$archive->{members}}) { next unless $m =~ /^Basic/; if ($delete) { push @to_be_deleted, $m; $deleted++; } next if $m =~ /\/script\-[A-Za-z]*\.xml$/; print "$m\n" if $list; $count++; if ($export) { my $module = odfDocument ( container => $archive, part => $m, read_only => 'true', element => 'script:module' ); my $filename = $m; $filename =~ s/[\/\\]/_/g; $filename =~ s/\.xml$/.bas/i; print "Exporting $filename\n" if $verbose; open BASIC, ">", $filename; print BASIC $module->{xpath}->root->string_value(); close BASIC; $module->dispose; } } if ($show_count) { if ($verbose) { print "Number of BASIC modules in the file: "; } print "$count\n"; } if ($deleted) { foreach my $member (@to_be_deleted) { print "Deleting $member\n" if $verbose; $archive->raw_delete($member); $manifest->removeEntry($member); } print "Writing the changes to $output\n" if $verbose; $archive->save($output); } exit; OpenOffice-OODoc-2.125/examples/text2odf0000644000175000017500000000161111206261427016115 0ustar jmgjmg#!/usr/bin/perl -w #----------------------------------------------------------------------------- # $Id : text2odf 0.2 2008-05-04 JMG$ #----------------------------------------------------------------------------- =head1 NAME text2odf - Text to OpenDocument conversion =head1 SYNOPSIS cat sourcefile.txt | text2odf targetfile.odt =head1 DESCRIPTION This filter creates an OpenDocument (ODF) file and fills it with the text coming through the standard entry. The target file is created, and any existing file with the same name is replaced. To avoid this behaviour and append the text to an existing ODF file, just remove the create => 'text' option in the script. =cut use OpenOffice::OODoc 2.101; die "Missing target filename\n" unless $ARGV[0]; my $doc = odfDocument(file => $ARGV[0], create => 'text'); while (my $line = ) { $doc->appendParagraph(text => $line); } $doc->save; exit; OpenOffice-OODoc-2.125/examples/text2table0000644000175000017500000000336411206261467016447 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : text2table 0.2 2008-05-13 JMG$ #----------------------------------------------------------------------------- =head1 NAME text2table - flat text conversion to OpenDocument spreadsheet =head1 SYNOPSIS text2table sourcefile.csv mycalc.ods 32 20 =head1 DESCRIPTION Creates an ODF spreadsheet file and populate its first sheet from a delimited text file. Each line of the source file produces a row in the target table. In the line, the field separator is ";". The target file is created. Any existing file with the same name is replaced. The two last arguments are the length (lines) and the width (columns) of the target table. The input values are processed as text values. =cut use OpenOffice::OODoc 2.103; my ($input_file, $output_file, $lines, $columns) = @ARGV; die "Usage : txt2table \n" unless ($input_file && $output_file && $lines && $columns); # create the OOo spreadsheet my $doc = odfDocument(file => $output_file, create => 'spreadsheet'); # select & size the 1st (and only) sheet in the document my $sheet = $doc->expandTable(0, $lines, $columns); # rename it as the input file (why not ?) $doc->renameTable($sheet, $input_file); # populate the table open INPUT, "<", $input_file or die "Input file $input_file unavailable\n"; my $line = undef; my @rows = $doc->getTableRows($sheet); for (my $i = 0 ; $i < $lines && ($line = ) ; $i++) { my $row = $rows[$i]; my @cellvalues = split ';', $line; my @cells = $doc->getRowCells($row); for (my $j = 0 ; $j < $columns && @cellvalues ; $j++) { $doc->cellValue($cells[$j], shift @cellvalues); } } # save the result $doc->save; exit; OpenOffice-OODoc-2.125/examples/odf_set_fields0000644000175000017500000000612411327773051017340 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odf_set_fields 0.4 2010-01-27 JMG$ #----------------------------------------------------------------------------- =head1 NAME odf_set_fields - Set names & values for user-defined fields =head1 USAGE odf_set_fields -options =head1 SYNOPSIS Sample script updating or creating 4 user-defined fields of an ODF file and adding new keywords. Existing keywords are preserved. Old user-defined fields names and values are deleted and replaced. The revision number of the document is incremented by 1. The keywords must be passed as a comma-separated list through the -keywords option. The user-defined fields/options are : -contact -organization -status -diffusion Examples: odf_set_fields foo.odt -contact "Donald Duck" -organization "Genicorp" odf_set_fields foo.odt -status "Complete" -keywords "software, office" =cut use OpenOffice::OODoc::Meta; use Getopt::Long; # default values for the user-defined fields (examples) my $contact = 'Corporate Editor'; my $organization = 'Foo Unlimited'; my $status = 'Draft'; my $diffusion = 'Public/Unclassified'; my $keywords = undef; # get the command line options GetOptions ( 'contact=s' => \$contact, 'organization=s' => \$organization, 'status=s' => \$status, 'diffusion=s' => \$diffusion, 'keywords=s' => \$keywords ); # get the command line argument as filename my $filename = $ARGV[0] or die "usage : odf_set_fields filename [-options]\n"; # create a meta-data object linked to the file my $doc = OpenOffice::OODoc::Meta->new(file => $filename) or die "I can't open $filename as an ODF document\n"; # set the user-defined fields using a list of name/value pairs # (the names are hard-coded in my example but they could be # dynamically defined just like the values) $doc->user_defined ( Contact => $contact, Organization => $organization, Status => $status, Diffusion => $diffusion ); # add the new keyword list (got from the -keyword command line option # if any). The 'keywords' method needs a list, so we have to split the # content of the -keywords option, assuming separator=comma # There is no risk to introduce some keyword redundancy here, because # the 'keywords' method from OpenOffice::OODoc::Meta adds only non-existing # keywords $doc->keywords(split(",", $keywords)) if $keywords; # because I'm sometimes a perfectionist, I want to # put the program signature in the 'generator' field (unavailable for # the end-user, but readable/updateable for OpenOffice::OODoc::Meta just as for # the OpenOffice.org software); useful to allow any other program to know # if this version comes directly from the StarOffice/OpenOffice suite or # has been generated by my program $doc->generator('odf_set_fields Version 0.4'); # with the same kind of idea, I want to # increment the editing cycles count (just to mimic the OpenOffice.org # software each time the file is edited, and maybe # to help some other workflow/versioning management program) $doc->increment_editing_cycles; # commit all and leave $doc->save; exit; OpenOffice-OODoc-2.125/examples/odfextract0000644000175000017500000000322311007421154016514 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odfextract 0.4 2008-05-04 JMG$ #----------------------------------------------------------------------------- =head1 NAME odfextract - Text selection and copy from one document to another one =head1 SYNOPSIS This sample script extracts every text element matching a given pattern (string or regex) from a source document and appends it to a target document. The target document must be an existing one (empty or not). The first argument of the command line is the source file name, the second one is the target file, the third one is the selection filter (i.e. any quoted string or Perl regular expression) =cut use OpenOffice::OODoc 2.101; die "Usage : odfextract source_file target_file filter\n" unless ($ARGV[0] && $ARGV[1] && $ARGV[2]); # document objects initialization print "Opening $ARGV[0] as source document...\n"; my $source_doc = odfText(file => $ARGV[0]) or die "$ARGV[0] is not a regular ODF file\n"; print "Opening $ARGV[1] as target document...\n"; my $target_doc = odfText(file => $ARGV[1]) or die "$ARGV[1] is not a regular ODF file\n"; # the imported elements will be appended to the document body my $root_element = $target_doc->getBody; # selection & transfer are done here print "Selecting the content matching $ARGV[2]...\n"; $target_doc->appendElement($root_element, $_) for $source_doc->selectElementsByContent($ARGV[2])->copy; # commit the changes in target file print "Saving the selection in $ARGV[1]...\n"; $target_doc->save; print "Job complete\n"; exit; #---------------------------------------------------------------------- OpenOffice-OODoc-2.125/examples/odf2pod0000644000175000017500000001272711322551523015723 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odf2pod 0.4 2010-01-11 JMG$ #----------------------------------------------------------------------------- =head1 NAME odf2pod - POD generation from an OpenDocument file =head1 SYNOPSIS Usage : odf2pod =head1 DESCRIPTION This demo script exports the content of a given OpenDocument file to POD on the standard output. In the present form, it's quite limited and not flexible, in order to remain easily readable. It should be considered as an example of text extraction using OpenOffice::OODoc and not as the 'definitive' odf2pod filter, knowing that complex document structures are not properly rendered. Before extraction, some transformations are done in the document in order to make it more convenient for a POD presentation. Some pieces of metadata (title, subject, description), if defined, are reported in the beginning of the POD. The footnotes are removed from the content and reported in a special section at the end. This script needs Text::Wrapper (that is not necessarily required by the OpenOffice::OODoc installation). To implement more sophisicated presentation rules, you could use Text::Format instead. =cut use OpenOffice::OODoc 2.101; use Text::Wrapper; #----------------------------------------------------------------------------- my $meta; # will be the metadata object my $doc; # will be the document content object #----------------------------------------------------------------------------- # text output utilities (using Text::Wrapper) my $paragraph_wrapper; my $list_wrapper; sub BEGIN # wrappers initialisation { # It's just an example; in a real application, the formatting rules # should be more flexibles and variables according to the style # of each source text element # Here, we prepair two kinds of wrappers, in order to have # a larger left margin for item lists than for ordinary paragraphs $paragraph_wrapper = Text::Wrapper->new ( columns => 76, par_start => ' ', body_start => ' ' ); $list_wrapper = Text::Wrapper->new ( columns => 76, par_start => ' ', body_start => ' ' ); } sub heading_output { my ($level, $text) = @_; $text && print "=head$level\t$text\n\n"; } # output the content according to the type of text object sub content_output { my $element = shift; # it's an ODF text object (not a flat string) my $text = $doc->getText($element); # choose an output format according to the type if ($element->isItemList) { print $list_wrapper->wrap($text) . "\n"; } # we use the paragraph output rule for any element # that is not a list else { print $paragraph_wrapper->wrap($text) . "\n"; } # in a more specialised script, we could select another # alternative wrapper according to the style (using the # getStyle() method of OpenOffice::OODoc::Text) } #----------------------------------------------------------------------------- # initialise the ODF file object my $ooarchive = odfContainer($ARGV[0]) or die "No regular ODF file\n"; # extract the metadata $meta = odfMeta(container => $ooarchive) or warn "This file has not standard ODF properties. Looks strange.\n"; # extract the content $doc = odfDocument(container => $ooarchive, part => 'content') or die "No standard ODF content ! I give up !\n"; # attempt to use some metadata to begin the output if ($meta) { my $title = $meta->title; if ($title) { heading_output(1, "NAME"); print $paragraph_wrapper->wrap($title) . "\n"; } my $subject = $meta->subject; if ($subject) { heading_output(1, "SUBJECT"); print $paragraph_wrapper->wrap($subject) . "\n"; } my $description = $meta->description; if ($description) { heading_output(1, "DESCRIPTION"); print $paragraph_wrapper->wrap($description) . "\n"; } # we could dump other metadata here... } # the strange 2 next lines prevent the getText() method of # OpenOffice::OODoc::Text (see the corresponding man page) from using # its default tags for spans and footnotes delete $doc->{'delimiters'}->{'text:span'}; delete $doc->{'delimiters'}->{'text:footnote-body'}; # here we select the tab as field separator for table field output # (the default is ";" as for CSV output) $doc->{'field_separator'} = "\t"; # in the next sequence, we will extract all the footnotes, store them for # later processing and remove them from the content my @notes = $doc->getFootnoteList; $doc->removeElement($_) for @notes; # get the full list of text objects (without the previously removed footnotes) my @content = $doc->getTextElementList; # if the first text element is not a heading, we create a leading # heading here, using the title or an arbitrary name heading_output(1, $meta->title || "INTRODUCTION") unless ($content[0]->isHeading); foreach my $element (@content) { my $level = $doc->getLevel($element); # get the hierarchical level if ($level) # if an element has a 'level', it's a heading { heading_output($level, $doc->getText($element)); } else { content_output($element); } } # all the document body is processed if (@notes) { # OK, we have some footnotes in store # create a special section heading_output(1, "NOTES"); my $count = 0; while (@notes) { $count++; my $element = shift @notes; my $text = "[$count] " . $doc->getText($element); print $paragraph_wrapper->wrap($text) . "\n"; } } # end of POD output print "=cut\n"; exit; #----------------------------------------------------------------------------- OpenOffice-OODoc-2.125/examples/odfmetadoc0000644000175000017500000002102411206261033016454 0ustar jmgjmg#!/usr/bin/perl -w #----------------------------------------------------------------------------- # $Id : odfmetadoc 0.3 2008-05-04 JMG$ #----------------------------------------------------------------------------- =head1 NAME odfmetadoc - Document properties management interface =head1 SYNOPSIS odfmetadoc =head1 DESCRIPTION Simple graphical user interface allowing the user to get and set the metadata of the OpenDocument and OpenOffice.org files (ODT, ODP, ODS, SXW, SXI, SXC,...) in a given directory. The list of the files is shown in the main window. Each time the user selects a file (by double-clic or OK button), another window appears, with some property fields: title, author, subject, description, keywords. The flat text content is shown in a read-only field. The user can update any property, or insert/delete any keyword. A double-click on a word in the text content field appends the word to the keword list. 'OK' saves the changes in the file. =head1 LIMITS This script is provided as a demo and test tool, with a very basic user interface. There is no directory selection dialog box. Some file names with blank spaces and/or non-ASCII characters are not properly processed by the graphical user interface in some environments (even if the OODoc API does support the same character sets as the local Perl installation). =head1 REQUIREMENTS Requires Tk and some Tk extensions (TK::Dialog, Tk::LabFrame, Tk::LabEntry) =cut #------------------------------------------------------------------------------ use Tk; use Tk::Dialog; use OpenOffice::OODoc 2.101; #------------------------------------------------------------------------------ our $sv_chemin = $ARGV[0] || $ENV{'HOME'} || '.'; our $chemin = $sv_chemin; our $D = undef; our $f = undef; our $F = undef; our $boite_liste = undef; our @liste = (); our $couleur_saisie = 'ivory'; our $couleur_lecture = 'LightSkyBlue'; our %delimiteurs = ( 'text:p' => { begin => undef, end => "\n" }, 'text:h' => { begin => '[Titre] ', end => "\n" }, 'text:list-item' => { begin => "\t* " }, %OpenOffice::OODoc::Text::DEFAULT_DELIMITERS ); #------------------------------------------------------------------------------ sub place_fenetre { my $fenetre = shift; my $x = int(($fenetre->screenwidth - $fenetre->reqwidth) / 2); my $y = int(($fenetre->screenheight - $fenetre->reqheight) / 2); $fenetre->geometry("+$x+$y"); $fenetre->resizable(0, 0); } #------------------------------------------------------------------------------ sub accesMeta { require Tk::LabFrame; require Tk::LabEntry; my $fichier = shift; my $oo_archive = odfContainer($fichier); return undef unless $oo_archive; my $doc = odfMeta(container => $oo_archive); return undef unless $doc; my $txt = odfText (container => $oo_archive, delimiters => { %delimiteurs }); my ($titre, $auteur, $sujet, $description, @clefs, $mot); $f->idletasks; $D = $f->Toplevel ( -title => $fichier, ); $D->withdraw; my $fr2 = $D->Frame -> pack(-side => 'bottom', -expand => 1, -fill => 'x'); my $fr1 = $D->Frame ->pack(-side => 'left', -anchor => 'n'); my $fr3 = $D->LabFrame ( -label => 'Keywords', -labelside => 'acrosstop' ) ->pack(-side => 'right', -expand => 1, -fill => 'y'); $fr1->LabEntry ( -label => 'Title', -textvariable => \$titre, -width => 40, -bg => $couleur_saisie ) -> pack(-side => 'top', -fill => 'x'); $fr1->LabEntry ( -label => 'Author', -textvariable => \$auteur, -width => 40, -bg => $couleur_saisie ) -> pack(-side => 'top', -fill => 'x'); $fr1->LabEntry ( -label => 'Subject', -textvariable => \$sujet, -width => 40, -bg => $couleur_saisie ) -> pack(-side => 'top', -fill => 'x'); my $fr4 = $fr1->LabFrame ( -label => 'Description', ) -> pack(-side => 'top', -fill => 'x'); $description = $fr4->Scrolled ( 'Text', -scrollbars => 'ose', -height => 3, -width => 80, -bg => $couleur_saisie, -exportselection => 'yes' ) -> pack(-side => 'top', -fill => 'both'); my $fr5 = $fr1->LabFrame ( -label => 'Content', ) -> pack(-side => 'top', -fill => 'x'); my $texte = $fr5->Scrolled ( 'Text', -scrollbars => 'ose', -height => 20, -width => 90, -bg => $couleur_lecture, -exportselection => 'yes' ) -> pack(-side => 'top', -fill => 'both'); my $l = $fr3->Scrolled ( 'Listbox', -scrollbars => 'ose', -height => 20, -bg => $couleur_saisie, -exportselection => 'yes' ) -> pack(-side => 'top', -expand => 1, -fill => 'x'); $texte->bind ( '' => sub { push @clefs, $texte->get ('insert wordstart', 'insert wordend'); $l->see('end'); } ); my $nm = $fr3->Entry ( -textvariable => \$mot, -width => 18, -bg => $couleur_saisie ) -> pack(-side => 'bottom', -fill => 'x'); $nm->bind ( '' => sub { if ($mot) { push @clefs, $mot; $l->see('end'); $mot = ""; } } ); $l->bind ( '<1>' => sub { $l->focus; }); $l->bind ( '' => sub { my $s = $l->curselection; $l->selectionClear($s) if defined $s; } ); $l->bind ( '' => sub { my $s = $l->curselection; $l->delete($s) if defined $s; } ); $fr2->Button ( -text => 'OK', -command => sub { $doc->title($titre); $doc->creator($auteur); $doc->subject($sujet); $doc->description ( $description->get('1.0', 'end') ); $doc->removeKeywords; $doc->keywords(@clefs); $doc->save; $doc->dispose; $txt->dispose; $D->destroy; } ) -> pack(-side => 'left', -expand => 1, -fill => 'x'); $fr2->Button ( -text => 'Cancel', -command => sub { $doc->dispose; $txt->dispose; $D->destroy; } ) -> pack(-side => 'left', -expand => 1, -fill => 'x'); $D->bind('' => sub { $D->destroy }); $D->bind('' => sub { $f->deiconify; }); $D->update; place_fenetre($D); $f->withdraw; $D->deiconify; tie @clefs, "Tk::Listbox", $l; @clefs = $doc->keywords; shift @clefs unless $clefs[0]; $titre = $doc->title; $auteur = $doc->creator; $sujet = $doc->subject; $description->delete('1.0', 'end'); $description->insert('end', $doc->description); if ($txt) { $texte->insert('end', "Loading the text..."); $D->idletasks; $texte->delete('1.0', 'end'); foreach my $contenu ($txt->selectTextContent) { $texte->insert('end', ($contenu . "\n")); } } else { $texte->insert('end', "**** CONTENT NOT AVAILABLE ****"); } $texte->configure(-state => 'disabled'); $D->grab; } #------------------------------------------------------------------------------ sub listeFichiers { $chemin = $sv_chemin unless $chemin; my $r = chdir $chemin; unless ($r) { warn "$chemin is not available ; back to $sv_chemin\n"; $chemin = $sv_chemin; chdir $chemin or warn "$chemin is not available\n"; } return $OpenOffice::OODoc::VERSION >= 2 ? <*.sx? *.od?> : <*.sx?>; } #------------------------------------------------------------------------------ $f = MainWindow->new ( title => 'Documents', ); $r1 = $f->Frame->pack(-side => 'bottom', -fill => 'x'); my $dir = $f->Entry(-width => 32, -textvariable => \$chemin) ->pack(-side => 'bottom', -expand => 'yes'); $dir->bind('', sub { @liste = listeFichiers; }); $boite_liste = $f->Scrolled ( 'Listbox', -scrollbars => 'oe', -width => 32, -height => 16, -bg => $couleur_saisie ) ->pack(-side => 'top', -expand => 'yes'); my $OK = $r1->Button ( -text => 'OK', ) ->pack(-side => 'left', -expand => 1, -fill => 'x'); $OK->configure ( -command => sub { my $f = $liste[($boite_liste->curselection)[0]]; $OK->configure ( -text => 'Loading...' ); accesMeta($f) if ($f); $OK->configure ( -text => 'OK' ); } ); $boite_liste->bind ( '' => sub { my $f = $liste[($boite_liste->curselection)[0]]; $OK->configure ( -text => 'Loading...' ); accesMeta($f) if ($f); $OK->configure ( -text => 'OK' ); } ); my $Annuler = $r1->Button ( -text => 'Cancel', -command => [ $f => 'destroy' ] ) ->pack(-side => 'left', -fill => 'x'); $f->bind('' => sub { $f->destroy }); tie @liste, "Tk::Listbox", $boite_liste; @liste = listeFichiers; $boite_liste->selectionSet(0); place_fenetre($f); $OK->focus; #------------------------------------------------------------------------------ MainLoop; exit; #------------------------------------------------------------------------------ OpenOffice-OODoc-2.125/examples/odfhighlight0000644000175000017500000001111411322553160017012 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odfhighlight 0.3 2010-01-11 JMG$ #----------------------------------------------------------------------------- =head1 NAME odfhighlight - search, replace and highlight text in a document =head1 SYNOPSIS odfhighlight "source.odt" "search string" -r "replacement" -o "target.odt" replaces "search string" by "replacement" in the file "source.odt", highlights each replacement with a yellow (default) backgound, then writes the resulting document as "target.odt" odfhighlight "myfile.odt" "search string" -color "green" highlights each occurrence of "search string" in "myfile.odt" with a green background color, without changing the text (without "-o" option, the changes apply to "myfile.odt" =head1 ARGUMENTS AND OPTIONS =head2 Default behaviour With the "minimal" command line, with only a filename and a string as arguments, each matching string is highlighted with a yellow background and represented with the "Standard" style. =head2 Options -e --encoding "xxxxxx" character set to use, if different from the default -r --replacement "new string" "new string" is used as a replacement for "search string" -c --color "code" an RGB color code, expressed either as the concatenation of 3 comma-separated decimal values (each one in the range 0..255, ex: "72,61,139" for a dark slate blue), or a 6-digit hexadecimal number, preceded by a "#" (ex: #00ff00 for green) or, if a colormap is available and known in your OpenOffice::OODoc installation, a symbolic color name (ex: "sky blue") -s --stylename "name" the name of the color style (default: "MyHighlight"); the user must provide a style name that is not already in use in the document -p --property "property=value" This option can be repeated; each occurrence gives an additional property for the highlight style (font name, size, foreground color, ...). For example, with the combination of -p 'fo:color=#ff0000' and -p 'fo:font-size=18pt', the highlighted text will be made of 18pt-sized, red characters. In order to master these options, you should have some knowledge of the Form Objects (FO) vocabulary that is used in the OpenDocument specification. -o --output "filename" -t --target "filename" an alternative filename to save the modified document, when the source document must remain unchanged =cut #----------------------------------------------------------------------------- use OpenOffice::OODoc 2.101; use Getopt::Long; #----------------------------------------------------------------------------- # getting the arguments and options my $encoding = $OpenOffice::OODoc::XPath::LOCAL_CHARSET; my $target = undef; my $replace = undef; my $color = '#ffff00'; # yellow my $stylename = 'MyHighLight'; my %properties = (); GetOptions ( 'encoding=s' => \$encoding, 'replacement=s' => \$replace, 'color=s' => \$color, 'stylename=s' => \$stylename, 'property=s' => \%properties, 'output|target=s' => \$target ); $color = odfColor($color) or die "Color code is not valid\n"; #----------------------------------------------------------------------------- my $filename = $ARGV[0]; my $search = $ARGV[1]; die "usage: odfhighlight [-options]\n" unless ($filename && $search); #----------------------------------------------------------------------------- # opening the document my $doc = odfDocument ( file => $filename, local_encoding => $encoding ) or die "File unavailable or non-ODF file\n"; #----------------------------------------------------------------------------- # creating the highlight style my $attr = $doc->{'opendocument'} ? 'fo:background-color' : 'style:text-background-color'; $properties{$attr} = $color; $doc->createStyle ( $stylename, family => 'text', properties => { %properties } ) or die "Please choose another style name.\n"; #----------------------------------------------------------------------------- # searching and replacing my @list = $doc->selectElementsByContent($search, $replace); #----------------------------------------------------------------------------- # coloring if (defined $replace) { # just in order to avoid unneeded metacharacter processing $replace =~ s/([\\\(\)\.\*\?\[\]\|\-])/\\$1/g; $search = $replace; } foreach my $element (@list) { $doc->setSpan($element, $search, $stylename); } #----------------------------------------------------------------------------- # saving the result $doc->save($target); exit; #----------------------------------------------------------------------------- OpenOffice-OODoc-2.125/examples/odf_set_title0000644000175000017500000000434311355427770017221 0ustar jmgjmg#!/usr/bin/perl #----------------------------------------------------------------------------- # $Id : odf_set_title 0.3 2010-01-27 JMG$ #----------------------------------------------------------------------------- =head1 NAME odf_set_title - Set the title of a document using the first heading of the content =head1 USAGE odf_set_title =head1 SYNOPSIS This sample script outputs the current title of an ODF file, then it replaces it using a command line argument as the new title. If the command line doesn't provide a new title, then the program uses the first heading text in the document body. If the document doesn't contain any heading element, nothing is changed. =cut use OpenOffice::OODoc 2.112; my $new_title = undef; # create an ODF file object # using the 1st command line argument as filename my $oofile = odfContainer($ARGV[0]); # exit if $filename isn't available or can't be open # as a regular ODF file die "Unavailable file $ARGV[0]\n" unless $oofile; # create a metadata-aware object, linked to the File object my $doc_meta = odfMeta(container => $oofile); # extract the title form the metadata object # (without argument, the 'title' method is a 'get' accessor) my $title = $doc_meta->title; # if the title is defined, display it and exit if (defined $title) { print "The existing title is \"$title\"\n"; } if (defined $ARGV[1]) { $new_title = $ARGV[1]; } elsif (! defined $title) { # use the 1st heading of the content # create a content-aware object linked to the same container my $doc_text = odfDocument ( container => $oofile, read_only => 'true' ); # get the text of the first heading element my $text = $doc_text->getHeadingText(0); if ($text) { # use this text, if defined, as the title $new_title = $text; } else { # there was no heading in the document, # so nothing is done warn "No heading text in the document.\n" . "Nothing is changed.\n"; } } # the new title (if any) is saved if (defined $new_title) { # set the new title print "The new title is \"$new_title\"\n"; $doc_meta->title($new_title); # update the revision number $doc_meta->increment_editing_cycles; # commit the update $oofile->save; } exit; OpenOffice-OODoc-2.125/oodoc_version0000755000175000017500000000062411100171163015404 0ustar jmgjmg#!/usr/bin/perl use strict; use OpenOffice::OODoc; sub display_date { return localtime(odfTimelocal(shift)); } my $version = $OpenOffice::OODoc::VERSION; my $pkgdate = display_date $OpenOffice::OODoc::BUILD_DATE; my $instpath = $OpenOffice::OODoc::INSTALLATION_PATH; print "OpenOffice::OODoc\n Version\t\t$version\n" . " Build date\t\t$pkgdate\n" . " Installation path\t$instpath\n"; exit; OpenOffice-OODoc-2.125/t/0000755000175000017500000000000011416644377013075 5ustar jmgjmgOpenOffice-OODoc-2.125/t/01write.t0000644000175000017500000001363011415135012014534 0ustar jmgjmg#----------------------------------------------------------------------------- # 01write.t OpenOffice::OODoc Installation test 2010-07-07 #----------------------------------------------------------------------------- use strict; use Test; BEGIN { plan tests => 19 } use OpenOffice::OODoc 2.124; ok($OpenOffice::OODoc::VERSION >= 2.124); #----------------------------------------------------------------------------- my $generator = "OpenOffice::OODoc " . $OpenOffice::OODoc::VERSION . " installation test"; my $testfile = $OpenOffice::OODoc::File::DEFAULT_OFFICE_FORMAT == 2 ? "odftest.odt" : "ootest.sxw"; my $class = "text"; my $image_file = "OODoc/data/image.png"; my $image_size = "7.06cm, 9.60cm"; my $test_date = odfLocaltime(); # Creating an empty new ODF file with the default template unlink $testfile; my $archive = odfContainer($testfile, create => $class); unless ($archive) { ok(0); # Unable to create the test file die "# Unable to create the test file\n"; } else { ok(1); # Test file created } #----------------------------------------------------------------------------- my $notice = "This document has been generated by the OpenOffice::OODoc " . "installation test. If you can read this paragraph in blue letters with " . "a yellow background, if you can see a centered image at the top of the " . "page, and if the informations below make sense, " . "your installation is probably OK."; my $title = "ODF sample document"; my $description = "Generated using OpenOffice::OODoc " . "$OpenOffice::OODoc::VERSION"; # Opening the content using OpenOffice::OODoc::Document my $doc = odfConnector ( container => $archive, part => 'content', readable_XML => 'true' ) or die "# Unable to find a regular document content\n"; ok($doc); # Document open and parsed my $styles = odfConnector ( container => $archive, part => 'styles', readable_XML => 'true' ) or die "# Unable to get the styles\n"; ok($styles); # Styles open and parsed # Creating a graphic style ok ( $styles->createImageStyle ( 'Centered Image', properties => { 'horizontal-pos' => 'center', 'vertical-pos' => 'from-top', 'wrap' => 'none', 'fo:margin-bottom' => '1cm' } ) ); # Inserting an image in the document ok ( $doc->createImageElement ( 'Logo', style => 'Centered Image', page => 1, size => $image_size, import => $image_file ) ); # Appending a page footer $styles->createStyle ( 'Centered Paragraph', family => 'paragraph', parent => 'Standard', properties => { 'fo:text-align' => 'center' } ); $styles->styleProperties ( 'Centered Paragraph', area => 'text', 'fo:font-size' => '60%' ); ok ( $styles->masterPageFooter ( 'Standard', $styles->createParagraph ( "Created by OpenOffice::OODoc\n" . localtime(), 'Centered Paragraph' ) ) ); # Appending a level 1 heading ok ( $doc->appendHeading ( text => "Congratulations !", level => "1", style => "Heading_20_1" ) ); # Creating a coloured paragraph style (blue foreground, yellow background) ok ( $styles->createStyle ( "Colour", family => 'paragraph', parent => 'Standard', properties => { -area => 'paragraph', 'fo:color' => odfColor(0,0,128), 'fo:background-color' => odfColor("yellow"), 'fo:text-align' => 'justify' } ) ); if ($doc->isOpenDocument) { $styles->styleProperties ("Colour", -area => 'text', 'fo:color' => '#000080'); } # Appending another paragraph using the new style ok ( $doc->appendParagraph(text => $notice, style => "Colour" ) ); # Appending another level 1 heading ok ( $doc->appendHeading ( text => "Your environment", level => 1, style => "Heading_20_1" ) ); # Appending a table showing some environment details my $table = $doc->appendTable("Environment", 6, 2); $doc->cellValue($table, "A1", "Platform"); $doc->cellValue($table, "B1", $^O); $doc->cellValue($table, "A2", "Perl version"); $doc->cellValue($table, "B2", $]); $doc->cellValue($table, "A3", "Archive::Zip version"); $doc->cellValue($table, "B3", $Archive::Zip::VERSION); $doc->cellValue($table, "A4", "XML::Twig version"); $doc->cellValue($table, "B4", $XML::Twig::VERSION); $doc->cellValue($table, "A5", "OpenOffice::OODoc version"); $doc->cellValue($table, "B5", $OpenOffice::OODoc::VERSION); $doc->cellValue($table, "A6", "OpenOffice::OODoc build"); $doc->cellValueType($table, "B6", 'date'); $doc->cellValue($table, "B6", $OpenOffice::OODoc::BUILD_DATE); # Appending another level 1 heading ok ( $doc->appendHeading ( text => "Your installation choices", level => 1, style => "Heading_20_1" ) ); # Appending a table with the installation parameters my $office_format = $OpenOffice::OODoc::File::DEFAULT_OFFICE_FORMAT == 2 ? "OASIS Open Document" : "OpenOffice.org 1.0"; my $color_map = $OpenOffice::OODoc::Styles::COLORMAP || ""; $table = $doc->appendTable("Choices", 4, 2); $doc->cellValue($table, "A1", "Local character set"); $doc->cellValue($table, "B1", $OpenOffice::OODoc::XPath::LOCAL_CHARSET); $doc->cellValue($table, "A2", "Working directory"); $doc->cellValue($table, "B2", $OpenOffice::OODoc::File::WORKING_DIRECTORY); $doc->cellValue($table, "A3", "RGB color map"); $doc->cellValue($table, "B3", $color_map); $doc->cellValue($table, "A4", "Default office document format"); $doc->cellValue($table, "B4", $office_format); # Opening the metadata of the document my $meta = odfMeta(container => $archive, readable_XML => 'on') or die "# Unable to find regular metadata\n"; ok($meta); # Writing some metadata elements ok($meta->title($title)); ok($meta->description($description)); ok($meta->generator($generator)); ok($meta->creation_date($test_date)); ok($meta->date($test_date)); $meta->creator($ENV{'USER'}); $meta->initial_creator($ENV{'USER'}); $meta->subject("OpenOffice::OODoc installation test"); $meta->removeUserProperties(); # Saving the $testfile file ok($archive->save); $doc->dispose; $styles->dispose; $meta->dispose; exit 0; OpenOffice-OODoc-2.125/t/02read.t0000644000175000017500000000442511415135042014323 0ustar jmgjmg#----------------------------------------------------------------------------- # 02read.t OpenOffice::OODoc Installation test 2010-07-07 #----------------------------------------------------------------------------- use strict; use Test; BEGIN { plan tests => 12 } use OpenOffice::OODoc 2.124; ok($OpenOffice::OODoc::VERSION >= 2.124); #----------------------------------------------------------------------------- my $testfile = $OpenOffice::OODoc::File::DEFAULT_OFFICE_FORMAT == 2 ? "odftest.odt" : "ootest.sxw"; my $generator = "OpenOffice::OODoc " . $OpenOffice::OODoc::VERSION . " installation test"; my $image_name = "Logo"; # Opening the $testfile file my $archive = odfContainer($testfile); unless ($archive) { ok(0); # Unable to get the $testfile file exit; } ok(1); # Test file open # Opening the document content my $doc = odfConnector(container => $archive); unless ($doc) { ok(0); # Unable to get a regular document content } else { ok(1); # Content parsed } # Opening the metadata my $meta = odfMeta(container => $archive); unless ($meta) { ok(0); # Unable to get regular metadata exit unless $doc; # Give up if neither content nor metadata } else { ok(1); # Metadata parsed } my $manifest = odfManifest(container => $archive); unless ($manifest) { ok(0); # Unable to get the manifest } else { ok(1); # Manifest parsed } # Checking the mime type my $mimetype = $manifest->getMainType || ""; if ($OpenOffice::OODoc::File::DEFAULT_OFFICE_FORMAT == 2) { ok($mimetype eq "application/vnd.oasis.opendocument.text"); } else { ok($mimetype eq "application/vnd.sun.xml.writer"); } # Checking the image element ok($doc->getImageElement($image_name)); # Selecting a paragraph by style ok($doc->selectParagraphByStyle("Colour")); # Getting the table my $table = $doc->getTable("Environment"); unless ($table) { ok(0); # unable to get the table } else { ok(1); # table found } my ($h, $w) = $doc->getTableSize($table); # Checking the table size; must be 6x2 ok(($h == 6) && ($w == 2)); # Checking cell value my $cv = $doc->cellValue($table, "B5"); ok($cv eq $OpenOffice::OODoc::VERSION); # Checking the installation signature in the metadata ok($meta->generator() eq $generator); $manifest->dispose; $doc->dispose; $meta->dispose; exit 0; OpenOffice-OODoc-2.125/OODoc.pod0000644000175000017500000005433511345413267014304 0ustar jmgjmg=head1 NAME OpenOffice::OODoc - The Perl Open OpenDocument Connector =head1 SYNOPSIS use OpenOffice::OODoc; # get global access to the content of an ODF file my $document = odfDocument(file => "MyFile.odt"); # select a text element containing a given string my $place = $document->selectElementByContent("my search string"); # insert a new text element before the selected one my $newparagraph = $document->insertParagraph ( $place, position => 'before', text => 'A new paragraph to be inserted', style => 'Text body' ); # define a new graphic style, to display images # with 20% extra luminance and color inversion $document->createImageStyle ( "NewImageStyle", properties => { 'draw:luminance' => '20%', 'draw:color-inversion' => 'true' } ); # import an image from an external file, attach it # to the newly inserted paragraph, to be displayed # using the newly created style $document->createImageElement ( "Image1", style => "NewImageStyle", attachment => $newparagraph, import => "D:\Images\Landscape.jpg" ); # save the modified document $document->save; =head1 DESCRIPTION This toolbox is an extensible Perl interface allowing direct read/write operations on files which comply with the OASIS Open Document Format for Office Applications (ODF), i.e. the ISO/IEC 26300:2006 standard. It provides a high-level, document-oriented language, and isolates the programmer from the details of the file format. It can process different document classes (texts, spreadsheets, presentations, and drawings). It can retrieve or update styles and images, document metadata, as well as text content. OpenOffice::OODoc is designed for data retrieval and update in existing documents, as well as full document generation. =head1 HOW TO USE THE DOCUMENTATION The present chapter, then the OpenOffice::OODoc::Intro one, should be read before any attempt to dig in the detailed documentation. The reference manual is provided in several separate chapters as described below. The OpenOffice::OODoc documentation, as the API itself, is distributed amongst several manual pages on a thematic and technical basis. The present section is a general foreword. Each manual page corresponds to a Perl module, with the exception of OpenOffice::OODoc::Intro. It's strongly recommended to have a look at the Intro before any other manual chapter, in order to get a quick and practical knowledge of the big picture. Another possible introductory reading has been published in The Perl Review (issue #3.1, dec. 2006) L, while an alternative presentation article, intended for French-reading users, can be downloaded at L The API is object-oriented and, with the exception of the main module (OpenOffice::OODoc itself), each module defines a class. The features of each module are documented in a manual page with the same name. But, while some classes inherit from other ones, they bring a lot of features that are not documented in the corresponding manual page. The best example is OpenOffice::OODoc::Document: it contains a few method definitions by itself, but it's the most powerful class, because it inherits from four other classes, so its features are documented in five manual pages. Fortunately, the classes are defined on a functional basis. So, for example, to know the text-related capabilities of a Document object, the user should select the Text manual page before the Document one. The detailed documentation of the API is distributed according to the following list: =head2 OpenOffice::OODoc The present manual page contains (in the GENERAL FUNCTIONS section below) the description of a small number of miscellaneous functions, dedicated to control some general parameters, to create the main objects of the applications, or to provide the user with some basic utilities. It introduces the main object constructors, namely odfContainer(), odfDocument() and odfMeta(). =head2 OpenOffice::OODoc::File This manual page contains detailed information about the physical access to the ODF containers, i.e. the multipart, compressed files that contain the documents. In some simple applications, this page can be ignored without risk. =head2 OpenOffice::OODoc::XPath It describes all the common features, that are provided by the corresponding class, and available in every other class with the exception of OODoc::File. This page is so called because it essentially relies on XPath expressions to select the various document elements. However, beyond the XPath-based retrieval features, it allows the user to update, create, or delete any element. This manual page describes the common XML toolbox of OpenOffice::OODoc. It allows almost everything, and it may be appropriate for XML- and XPath-aware users who have some knowledge of the Open Document Format. On the other hand, it covers only the low level part of the API. The high level part of OpenOffice::OODoc is covered by the ::Text, ::Image, ::Styles, ::Document and ::Meta manual chapters. However, the user should remember that ::XPath describes some common features which are not redundantly documented in the high level chapters , so this manual page can be useful even if the user don't need to work with explicit XPath expressions. Note: by "high level", I don't suggest that OpenOffice::OODoc provides any stratospheric functionality. The "high level" API is a set of frequently needed features that are implemented in order to be used without knowledge of the ODF storage structure and without XML familiarity. The following example, that retrieves a section in a text document according to its name, illustrates the difference: while both return the same result, the second instruction, which is mnemonic and largely self-documented, belongs to a higher level than the first one: $section = $doc->getElement('//text:section[@text:name="Foo"]'); $section = $doc->getSection("Foo"); =head2 OpenOffice::OODoc::Text This manual page describes all the high level text processing methods and allows the user's program to deal with all the text containers (headers, paragraphs, item lists, tables, and footnotes). OpenOffice::OODoc::Text is dedicated to the text content and structure of any kind of document, and *NOT* to the so-called "text documents". As a consequence, this chapter describes all the methods which process ODF text containers in spreadsheets and presentations as well as in text documents. The set of covered text objects includes all the markup elements that may be inserted within paragraphs, such as variable text fields, notes, bibliography entries, bookmarks, index entries, text runs with special styles, hyperlinks, etc. Whatever the document class, the ::Text part of OpenOffice::OODoc may apply to some style definitions, too. For example, a page style may specify a header and/or a footer that may contain paragraphs and other text elements. =head2 OpenOffice::OODoc::Image This manual page describes all the graphics manipulation API, i.e. all the available syntax dedicated to insert or remove images in the documents, and to control the presentation of these images. Note that OpenOffice::OODoc does *NOT* include any graphical processing API; it just deals with the Open Document way to include and display images. It allows, for example, to control the color correction and the display size of an image in the context of a particular document, but not to change the image itself. =head2 OpenOffice::OODoc::Styles This manual page describes the methods to be used to control the styles of a document, knowing that each page layout, each text element, and each image is displayed or printed according to a style. This part of the documentation can be ignored if the user's programs are strictly content- focused and don't care with the presentation. Note that some style definitions, such as master pages, can include text containers or images, which can be processed through methods provides by OpenOffice::OODoc::Text or OpenOffice::OODoc::Image. =head2 OpenOffice::OODoc::Document This manual page describe some miscellaneous methods that deal simultaneously with text, presentation and/or images. So, in order to discover the capabilities of a "Document" object (created with ooDocument), the user should use the Text, Image, Styles AND Document manual pages. The OpenOffice::OODoc::Document class inherits all the features provided by the other classes with the exceptions of OpenOffice::OODoc::File and OpenOffice::OODoc::Meta. =head2 OpenOffice::OODoc::Meta This manual page describes all the available methods to be used in order to control the global properties (or "metadata") of a document. Most of these properties are those an end-user can get or set through the "File/Properties" command with the OpenOffice.org desktop software. =head2 OpenOffice::OODoc::Manifest This manual page describes the manifest management API, knowing that the manifest, in an ODF file, contains the list of the file components (or "members") and the media type (or MIME) of each one. The text content, the style definitions, the embedded images, etc. are each one stored as a separate "member". =head1 GENERAL FUNCTIONS (EXPORTED) =head3 odfConnector() Synonym: odfDocument() =head3 odfContainer($filename) Shortcut for OpenOffice::OODoc::File->new(). This function returns a File object, that is the object representation of the physical package containing the text, the images and the style definitions of an ODF document. See the OpenOffice::OODoc::File manual page for detailed syntax. See the OpenOffice::OODoc::Intro manual page to know why, in some situations, the using applications need or don't need to deal with explicit File objects. Synonyms: odfFile(), odfPackage(). =head3 odfDecodeText($ootext) Returns the translation of a raw ODF (UTF-8) string in the local character set. While the right translation is automatically done by the regular text read/write methods of OpenOffice::OODoc, this function is useful only if the user's application needs to bypass the API. =head3 odfDocument() Shortcut for OpenOffice::OODoc::Document->new(). This function is the most general document constructor. It creates and returns a new Document object. It can be instantiated on the basis of an existing ODF file, or using XML, OpenDocument-compliant data previously loaded in memory. With an appropriate "create" parameter, it can be used in order to create a new document from scratch as well. The Document class provides methods allowing a lot of read/update operations in the text content, the graphics, and the presentation. So ooDocument() is the recommended first call to get access to a document for further processing. See the OpenOffice::OODoc::Document manual page for detailed syntax. =head3 odfEncodeText($ootext) Returns the translation of an application-provided string, made of local characters, in an ODF-compliant (UTF-8) string. The given string must comply with the active local encoding (see odfLocalEncoding()). While the right translation is automatically done by the regular text read/write methods of OpenOffice::OODoc, this function is useful only if the user's application needs to bypass the API. =head3 odfFile($filename) Synonyms: odfContainer(), odfPackage(). =head3 odfImage() Shortcut for OpenOffice::OODoc::Image->new(). Generally not used explicitly by the applications. This function returns a document object whose features are related to image element processing, which is a subset of the Document object. See the OpenOffice::OODoc::Image manual page for detailed syntax. =head3 odfLocalEncoding([character_set]) Accessor to get/set the user's local character set (see $OpenOffice::OODoc::XPath::LOCAL_CHARSET in the OpenOffice::OODoc::XPath man page). Example: $old_charset = odfLocalEncoding(); odfLocalEncoding('iso-8859-15'); If the given argument is an unsupported encoding, an error message is produced and the old encoding is preserved. So this accessor is safer than a direct update of the $OpenOffice::OODoc::XPath::LOCAL_CHARSET variable. The default local character set is fixed according to the "OODoc/config.xml" file of your local OpenOffice::OODoc installation (see readConfig() below), or to "iso-8859-1" if this file is missing or doesn't say anything about the local character set. By calling ooLocalEncoding() with an argument, the user's programs can override this default. Note: the user can override this setting for a particular document, using the 'local_encoding' property of the document object (see the OpenOffice::OODoc::XPath manual page). See the Encode::Supported (Perl) documentation for the list of supported encodings. =head3 odfLocaltime() Converts the numeric time given in argument to an ISO-8601 date (aaaa-mm-jjThh:mm:ss), knowing that this format is required for the stored content of any ODF-compliant date element or attribute. The argument type is the same as for the standard Perl localtime() function, i.e. a number of seconds since the "epoch". It can be, for example, a value previously returned by a time() call. Without argument, returns the current local time in ISO-8601 format. Beware: The resolution of this function is limited to the second, unlike the ISO-8601 standard which supports an optional subsecond field. =head3 odfManifest() Short cut for OpenOffice::OODoc::Manifest->new(). This function returns a Manifest object, giving access to the meta-information of the physical archive containing the document. =head3 odfMeta() Shortcut for OpenOffice::OODoc::Meta->new(). This function returns a Meta object. Such an object represents the global properties, or "metadata", of a document. It brings a set of accessors allowing the user to get or set some properties such as the title, the keyword, the description, the creator, etc. See the OpenOffice::OODoc::Meta manual page for details. =head3 odfPackage($filename) Synonyms: odfContainer(), odfFile(). =head3 odfReadConfig([filename]) Creates or resets some variables of the API according to the content of an XML configuration file. Without argument, this function looks for 'OODoc/config.xml' under the installation directory of OpenOffice::OODoc. In any case, the provided file must have the same XML structure as the config.xml file included in the distribution, so: my_charset my_colormap_file my_path 2 cpan_package_build_date my_installation_date In the example above, "my_oo_date" should be replaced by a regular ISO-8601 date (YYYY-MM-DDThh:mm:ss). Elements out of the element are ignored. Any element included in sets or update a variable with the same name and the given value in the space of the OpenOffice::OODoc package. So, for example an element like a strange value will make a new $OpenOffice::OODoc::strange_thing variable, initialized with the string "a strange value", available for any program using OpenOffice::OODoc. Attributes and sub-elements are ignored. Strings with characters larger than 7 bits must be encoded in UTF-8. Any '-' character appearing in the name of an element is replaced by '::' in the name of the corresponding variable, so, for example, the element controls the initial value of $OpenOffice::OODoc::XPath::LOCAL_CHARSET. All the variables defined in this file, are the file itself, are optional. The element is not used by the API; it's provided for information only. It allows the user to get (in OpenOffice format) the date of the last installation of OpenOffice::OODoc, through the variable $OpenOffice::OODoc::INSTALLATION_DATE. In the default config.xml provided with the distribution, this element contains the package generation date. The element is the date of the CPAN package. This function is automatically executed as soon as OpenOffice::OODoc is used, if the OODoc/config.xml configuration file exists. =head3 odfStyles() Shortcut for OpenOffice::OODoc::Styles->new(). Generally not used explicitly by the applications. This function returns a Styles object, that brings a subset of the Document object. In can be used in place of odfDocument() if the calling application needs some style/presentation manipulation methods only. Note the 's' at the end of 'Styles': this object doesn't represent a particular style; it represents a set of styles related to a document. See the OpenOffice:OODoc::Styles manual page for detailed syntax. =head3 odfTemplatePath([path]) Shortcut for OpenOffice::OODoc::File::templatePath(). Accessor to get/set an alternative path for the ODF template files used to create new documents. The template path must designate a directory containing 4 regular ODF files, each one corresponding to an supported ODF document class, i.e. "template.odt", "template.ods", "template.odp", "template.odg". =head3 odfText() Shortcut for OpenOffice::OODoc::Text->new(). Generally not used explicitly by the applications. This function returns a Text object, that brings a subset ot the Document object. It can be used in place of ooDocument() if the calling application is only text-focused (i.e. if it doesn't need to deal with graphics and styles). The processed document can contain (and probably contains) graphics and styles, but the methods to process them are simply not loaded. See the OpenOffice::OODoc::Text manual page for detailed syntax. =head3 odfTimelocal() Translates an ODF-formatted date (ISO-8601) into a regular Perl numeric time format, i.e. a number of seconds since the "epoch". So, the returned value can be processed with any Perl date formatting or calculation function. Example: my $date_created = odfTimelocal($meta->creation_date()); $lt = localtime($date_created); $elapsed = time() - $date_created; print "This document has been created $date_created\n"; print "$elapsed seconds ago"; This sequence prints the creation date of a document in local time string format, then prints the number of seconds between the creation date and now. Note that the creation_date() method used here works with the meta-data document part only (see OpenOffice::OODoc::Meta for details about this method). Note: This function requires the Time::Local Perl module. =head3 odfWorkingDirectory([path]) Accessor to get/set the working directory to use for temporary files. Short-lived temporary files are generated each time the save() function (see OpenOffice::OOdoc::File) is called. If case of success, these files are automatically removed when the call returns, so the user can't view them. If something goes wrong during the I/O processing, the temporary files remain available for debugging. In any case, a working directory is necessary to create or update documents. However, OpenOffice::OODoc can be used without available working directory in a read-only application. The default working directory depends on the "OODoc/config.xml" file of your local OpenOffice::OODoc installation. If this file is missing or if it doesn't contain a element, the working directory is "." (i.e. the current working directory of the user's application). If an argument is given, it replaces the current working directory. A warning is issued if the (existing or newly set) path is not a directory with write permission. After this warning, the user's application can run, but any attempted file update or creation fails. This accessor sets only the default working directory for the application. A special, separate working directory can be set for each OOo document (see the manual page for OpenOffice::OODoc::File for details, if needed). CAUTION: a odfWorkingDirectory() call can't change the working directory of a previously created File object. So, consider the following code sequence: my $doc0 = ooDocument(file => 'doc0.odt'); odfWorkingDirectory('C:\TMP'); my $doc1 = ooDocument(file => 'doc1.odt'); In this example, all the write operations related to the $doc0 document will use the default working directory, while the ones related to $doc1 will use "C:\TMP". =head3 odfXPath() Shortcut for OpenOffice::OODoc::XPath->new(). Generally not used explicitly by the applications. This function returns an XPath object, that brings all the low level XML navigation, retrieve, read and write methods of the API. The XPath class (in the OpenOffice::OODoc context) is an OpenOffice-aware wrapper for the general XML::Twig API. Unless you are a very advanced user and you have a particular hack in mind, you should never need to explicitly create an XPath object. But you must know that every method or property of this class is inherited by the Text, Image, Styles, Document and Meta objects. So the knowledge of the corresponding manual page could be useful. See the OpenOffice::OODoc::XPath manual page for detailed syntax. =head3 ooDocument() Synonyms: odfDocument(), odfConnector(). =head3 ooDecodeText() See odfDecodeText(). =head3 ooEncodeText() See odfEncodeText(). =head3 ooFile($filename) Synonyms: odfContainer(), odfPackage(), odfFile(). =head3 ooImage() Synonym: odfImage(). =head3 ooLocalEncoding() See odfLocalEncoding(). =head3 ooLocaltime([$time_value]) See odfLocaltime() =head3 ooManifest() Synonym: odfManifest(). =head3 ooMeta() Synonym: odfMeta(). =head3 ooReadConfig() See odfReadConfig(). =head3 ooStyles() Synonym: odfStyles(). =head3 ooTemplatePath() See odfTemplatePath(). =head3 ooText() Synonym: odfText(). =head3 ooTimelocal($oodate) See odfTimelocal() =head3 ooWorkingDirectory() See odfWorkingDirectory(). =head3 ooXPath() Synonym: odfXPath(). =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2008 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/LICENCE0000644000175000017500000000027311063653372013612 0ustar jmgjmg This software is free software. It is subject to the terms and conditions of the GNU Lesser General Public Licence, version 2.1, of the Free Software Foundation (http://www.fsf.org). OpenOffice-OODoc-2.125/Changes0000644000175000017500000004570711416643537014137 0ustar jmgjmg2.125 2010-07-12 XPath.pm: bug #57888 fix Text.pm: bug fix in selectElementByContent() Styles.pm: bug fix in setBackgroundImage() All: code cleaning 2.124 2010-04-02 Meta.pm: minor accessor improvement. Text.pm: bug fixes in getTextElementList(), setSpan(), removeSpan(); removeTextStyleChanges() introduced as an alias of removeSpan(); textStyle(), getText() improvements. Text.pm: functional change in updateText() (user-provided function call). XPath.pm: improved getTextDescendants(), added getTextNodes(). 2.123 2010-03-20 XPath.pm: getElementList() becomes an alias of selectNodesByXPath(). XPath.pm: improved setChildElement(), selectChildElements(), added setChildElements(), newTextNode(). Manifest.pm: getEntry() optimization. Text.pm: added updateText(); fixed a doc error. Moved replaceText(), substituteText() from XPath.pm to Text.pm. 2.122 2010-03-10 Minor bug fix. 2.121 2010-03-10 Intro.pod: doc update. Styles.pm: createStyle() bug fix; now 'check' is no longer 'true' by default with createStyle(). Text.pm, XPath.pm: moved some methods from Text to XPath. Text.pm: various improvements and compatibility break with setNote(), setBibliographyMark(), setBookmark(), setHyperlink(), setAnnotation(), setTextField(), getUserFieldReferences(); deprecated setSpan(). Text.pm: added setRangeMark(), checkRangeMark(), deleteMark(), deleteMarks(), checkRangeBookmark(), deleteBookmark(), setIndexMark(), checkIndexMark(), deleteIndexMark(), setTextSpan(), setTextSpans(), setIndexMark(), setTextFields(), setUserFieldDeclaration(). XPath.pod: splitElement() doc error fix. XPath.pm: Added splitContent(), setChildElement(), textIndex(), getPartName(); added element identifier accessors & id-based retrieval; improved getRoot(), context-related methods, objectName(), selectElementByAttribute(), selectElementsByAttribute(), selectElementByContent(), selectElementsByContent(), getUserFields(). Examples: oodoc_test now allows target file name as argument. 2.112 2010-01-27 Meta.pm: Added increment_editing_cycles(). Text.pm: Bug fix in userFieldValue() #54004. Cosmetic change in odf_set_fields example. Bug fix and cosmetic change in odf_set_title example. 2.111 2010-01-10 File.pod, XPath.pod: doc update. File.pm: target file no longer require at creation time. File.pm: fixed bug #53552, real support for filehandles. XPath.pm: changes related to filehandle support and bug #53552. XPath.pm: utf8 becomes the default local character set. INSTALL: doc update. 2.109 2010-01-05 Cosmetic changes in the installation tests; changed the test image. Updated template documents. Makefile.PL: Updated dependencies (updated INSTALL accordingly). Makefile.PL: utf8 becomes the default character set. File.pm: Archive::Zip 1.18 now required; IO::File required; file handles supported. Meta.pm: Added user-defined property access methods; added getTemplate(), unlinkTemplate(). Text.pm: Added setNote(), fixed a bug in getTableByName() [#48356]. 2.108 2009-05-24 Some doc cleaning Meta.pm: added getUserProperty(), setUserProperty(), removeUserProperty() XPath.pm: added new check in constructor according to [#43744] XPath.pm: avoided a useless warning in getAttributes() [#42038] Text.pm: improved expandTable() according to [#41975] (thanks to Barry Slaymaker), changed getTable(), some code cleaning Image.pm: importImage() now can automatically import externally linked images File.pm: save() now checks the source files availability 2.107 2008-12-07 Text.pm: added getCellPosition(), adapted from a proposal by dhoworth Makefile.PL: updated the XML::Twig version requirement Makefile.PL: (grrr...) removed the accent in the author's name, due to a Solaris-specific trouble with non-ASCII characters in the makefiles XPath.pm: now uses XML::Twig 3.32 instead of 3.22 2.106 2008-11-05 Added the oodoc_test example Moved odfLocatime() and odfTimeLocal() from Meta.pm to XPath.pm, and updated Meta.pod, OODoc.pod and XPath.pod accordingly XPath.pm: added openDocumentVersion() XPath.pm: fixed a bug in isPresentation() XPath.pm: added automatic replacement by 0 of any constructor parameter set to 'off' or 'false' by the calling application XPath.pm: added multiple spaces handling in setText() XPath.pm: normalized the content produced by setText() and extendText() Text.pm: added setAnnotation() Text.pm: added getTableByName() Reverted the (ugly and buggy) use/require changes in examples Some doc cleaning (README, INSTALL) Removed useless install date info from oodoc_version 2.105 2008-10-08 No code change; just fixed a small doc mistake 2.104 2008-10-08 Added the oodoc_version executable script Replaced "use" by "require" for Tk and Text::Wrapper in examples, in order to avoid some non essential installation dependencies. Text.pm: bug fix in extendText() (#39174) Perl >= 5.8 required in every PM file Licensing: now GNU-LGPL 2.1 only 2.103 2008-06-15 Some cosmetic changes in the installation tests Makefile.PL, config.xml: added BUILD_DATE Meta.pm: added version() Styles.pm: added getFontDeclarations(), getFontName() Styles.pm: bugfix related to non-ASCII characters in style names File.pm: changed the way of creating new documents Replaced XML templates by ODF and OOo files (including a few paragraph styles) XPath.pm: given priority to 'member' option over 'part' option, to 'archive' over 'container', for legacy reasons; other parameter priority changes; code cleaning in the constructor XPath.pm: bugfix related to non-ASCII characters in frame names Text.pm: bugfixes related to non-ASCII characters in styles and draw page names Text.pm: removed some code redundancies in table cell related accessors Text.pm: added updateUserFieldReferences() Text.pm: added expandTable() Text.pm: removed an undesirable warning in getTable() Text.pm: fixed a bug in getTableCell() Examples: Changed text2table in order to use expandTable() 2.102 2008-05-04 Cleaned some code examples 2.101 2008-05-04 Intro.pod: doc update Meta.pm: fixed wrong exports for ooLocaltime() and ooTimelocal(), added odfXxx aliases for deprecated ooXxx functions Meta.pod: doc update Styles.pm: added new odfXxx aliases for deprecated ooXxx global functions Styles.pod: doc update; fixed #29578 OODoc.pm: added new odfXxx aliases for deprecated ooXxx global functions OODoc.pod: doc update Text.pm: added support for time values in table cells, improved the table normalization feature XPath.pm: added 'container' and 'part' attributes ('archive' and 'member' are now deprecated) Makefile.PL: added PACKAGING_DATE All modules, docs and examples: various renamings 2.035 2007-06-30 Text.pm: Bug fixes related to some table cell attribute accessors OODoc.pm: Added new aliases (oo -> odf) Styles.pod: doc update 2.034 2007-03-17 2.033 2007-03-17 - XPath.pm: bug fix in replicateNode() (#25271) - Styles.pm: improved style retrieval by display-name - Documentation update (Styles.pod, XPath.pod, Intro.pod) 2.032 2007-01-19 - XPath.pm: bug fix in getText() - XPath.pm: "part" allowed as synonym of "member" in constructor - XPath.pm: "attributes" allowed as synonym of "attribute" in insertElement() 2.031 2007-01-10 - Text.pm: bug fix in cellSpan (#24277) - XPath.pm: improved getLocalPosition() 2.029 2006-12-18 - Text.pm: bug fix in cellSpan() (#23501); improved removeHeading() 2.028 2006-09-07 - XPath.pm: bug fix in selectChildElements() 2.027 2006-08-03 - Added the oofindbasic example; added oo_set_fields, oo_set_title, oo_search as executable examples - Styles.pm: Added masterPageExtension(); changed masterPageHeader() and masterPageFooter(); added masterPageHeaderLeft() and masterPageFooterLeft(), following a Volker Hirsinger's suggestion - XPath.pm: Fixed a bug in removeAtribute() - XPath.pm: Fixed a bug which prevented accessing 'styles' when $1 was set, thanks to Dab - XPath.pm: More context-sensitive get_xpath calls; some code lightening and optimization - Text.pm: Fixed a bug in userFieldValue(), thanks to Andy Layton - Text.pm: bi-directional span allowed by cellSpan() - Text.pm: Fixed a bug in getTextElementList() - Text.pm: code lightening 2.026 2006-06-11 - XPath.pm: added pickUpChildren(), appendElements(), moveElements() - 01read.t: Bug fix in MIME type check - Text.pm: Added makeHeading(), textField(), setTextField(), moveElementsToSection(); Optimized setSpan(); replaced getChapter() by getChapterContent() - Text.pod: createParagraph() now documented - Styles.pm: Bug fix in getDefaultStyle() 2.025 2006-05-05 - Image.pm: Some updates due to changes in XPath.pm - XPath.pm: Added getFlatText(), setFlatText(), insertTextChild(), getDescendants(), createFrame(), and frame geometry methods - Image.pm: Cosmetic improvement in imagePosition() - Styles.pm: Added rgbColor() and odfColor() - Styles.pm: Added getOutlineStyleElement(), updateOutlineStyle() - Styles.pm: "display-name" bug fix - XPath.pm: Added frame related methods - XPath.pm: Added setFlatText(), splitTextElement(), blankSpaces(), lineBreak(), tabStop() - Meta.pm: Fixed an issue with tab stops and line breaks - Text.pm: Added text-box related methods - Text.pm: Added setBibliographyMark() - Text.pm: More context-sensitive selectXxx() methods - Text.pm: Improved extendText(), allowing insertion within an element - Text.pm: Improved getText(), setText() - Text.pm: Improved list-related features - Text.pm: Added 'within' option in insertElement() - Text.pm: Bug fix and new methods related to footnotes & endnotes - Text.pm: added selectElementByTextId() - Text.pm: "Header" to "Heading" replacement when appropriate - Text.pm: Added setBookmark() as a synonym for bookmarkElement() - Documentation cleaning and update 2.024 2006-03-17 - Text.pm: Zero or blank string allowed in createParagraph() - Text.pm: Added getCellParagraph() - Bug fix in XPath.pm in order to allow setText() with a zero value - Bug fix in XPath.pm in order to allow element creation with an initial zero or blank value - Bug fix in File.pm (raw_delete(), save()); added read_only flag - Bug fixes in removeAttribute() and setAttributes() (thanks to Paul Tyers) - Documentation cleaning 2.023 2006-02-10 - Styles.pm: Bug fix in styleName() (#17543) - Text.pm: added unlockSections() - Documentation update 2.022 2006-02-04 - Text.pm: added some improvements and fixed an issue in setSpan() - Text.pm: added an improved version of extendText() - Text.pm: fixed bugs in deleteTable() and defaultOutputTerminator() (thanks to ) - Text.pm: added getSectionList(), sectionName() - XPath.pm: fixed an issue in setText() and extendText() - XPath.pm: added appendTabStop(), appendLineBreak(), appendSpaces() - Text.pod: Doc error fixes and updates 2.021 2006-01-21 - Text.pm: added section lock/unlock and sub-document link features - Documentation cleaning & update - Archive::Zip requirement downgraded to 1.14 - Makefile.PL cleaning - The default file format is now OpenDocument (File.pm & Makefile.PL) - Cosmetic changes in XPath.pm 2.019 2006-01-02 - Text.pm: bug fix in getTable() (#16864) 2.018 2005-12-10 - XPath.pm: small fix; added getLocalPosition() as an Element method. - Text.pm: Added copyRowToHeader(), deleteTableColumn(); code cleaning in insertTableColumn(); improved normalizeSheet(); bug fixes in insertRow(), getItemListText(). - Doc cleaning and update; removed Notes.pod. 2.017 2005-11-27 - XPath.pm: Added context change features - Text.pm: Bug fix in getList(). Added getItemListText() and changed getText() for item lists. Improved getHeaderTextList, improved level selection in every getHeaderXXX method. Added section-focused methods. Added variable-focused methods. 2.016 2005-11-19 - File.pm: improvement/fix allowing multiple save() calls - Archive::Zip 1.16 required 2.015 2005-11-13 - Text.pm: bug fix in isTableRow() (#15739) - Text.pm: Added insertTableColumn() 2.014 2005-10-31 - Text.pm: Bug fix in getTable(). Added getTableHeaderRow(). 2.013 2005-10-22 - Code cleaning in Image.pm - Text.pm: Added hyperlinkURL() 2.012 2005-10-22 - Added OpenOffice::OODoc::Element package - XML::Twig 3.22 required - Text.pm: Added tableName(), getCellParagraphs(), setHyperlink(), selectHyperlinkElement(), selectHyperlinkElements() and fixed a bug in removeSpan() - Doc update 2.011 2005-09-21 - Fixed an issue related to style copying from one doc to another one and improved the style importation feature in createStyle (Styles.pm) - Added font declaration processing methods (Styles.pm) 2.009 2005-09-17 - Bug fix in insertParagraph (Text.pm) 2.008 2005-09-16 - Changed getText() and getCellValue() in order to export the full text content of multi-paragraph table cells (Text.pm) - Bug fix in Styles.pm (masterPageHeader, masterPageFooter) - Changed getText(), extendText() and created createSpaces() in XPath.pm in order to improve the management of repeated spaces - Doc update 2.007 2005-09-12 - Bug fix in Text.pm (wrong cell addressing in rows containing covered cells) 2.006 2005-08-27 - Bug fix in Text.pm (setSpan) - Bug fix and update in oometadoc example - Bug fix in empty directory management in archives (File.pm) - Various doc debugging and update 2.005 2005-08-17 - Bug fix in XPath.pm (setAttribute) - Logic change in Text.pm (userFieldAttribute) - Various cleaning 2.003 2005-08-15 - Table cell value & style processing improvements for ODF - Fixed a bug and improved page style vocabulary in Styles.pm - Improved support for ODF in Styles.pm - Bug fix in XPath.pm (getAttributes) - Doc update - First CPAN version supporting ODF 2.002 2005-07-07 [developer release] - Bug fix in XPath.pm (replicateElement) 2.001 2005-06-28 [developer release] - Bug fixes (item lists in OD) - Doc update, mainly about OOo/OD functional differences 2.000 2005-05-19 [developer release] - OASIS Open Document (OD) support - XML template management change 1.309 2005-05-03 - Fixed a bug that prevented new OOo file creation when the installation full path included spaces - thanks to imacat[at]mail.imacat.idv.tw. 1.308 2005-04-30 - Bug fix in Image.pm (exportImages) - Documentation update 1.307 2005-03-31 - Downgraded Archive::Zip version control to 1.06 (1.14 seems to be a problem for some testers) - Added initial_creator() in Meta.pm 1.306 2005-03-16 - Added userFieldValue(), getChapter(), bibliography and bookmark related methods in Text.pm - Modified getHeader() in Text.pm - Documentation update 1.305 2005-03-01 - Added getParentStyle() and getAncestorStyle() in Styles.pm - Added Archive::Zip version control (1.14) in Makefile.PL - ABSTRACT in Makefile.PL - Bug fix and optimization in the text2table example 1.304 2005-02-18 - Bugfix in the oometadoc example - Code cleaning and optimizations (Text.pm) 1.303 2005-02-17 - Added a Tk graphical example (oometadoc) - Fix some module version errors 1.302 2005-02-17 - Improved flat XML export facilities - Performance tuning (XPath.pm, Text.pm) 1.301 2005-02-07 - Migration to XML::Twig (a lot of rework in XPath.pm) - Optimized the element creation process - Added flat XML files input option - Many internal changes in Text.pm due to XML::Twig - A few internal changes in other modules due to XML::Twig - Added examples; put some examples in EXE_FILES - Documentation update - Makefile.PL update; XML::XPath no longer required: XML::Twig required 1.207 2005-01-28 - Fixed a normalizeSheet() issue in Text.pm - Added a few more shortcuts in XPath.pm - Some optimizations in XPath.pm and Text.pm 1.206 2005-01-27 - Documentation update - Added several spreadsheet management features in Text.pm - "readable XML" option in XPath.pm - Row/cell addressing improvements for spreadsheets in Text.pm - Added cellSpan() in Text.pm - Bug fixes in getCellValue() and updateCell() in Text.pm - Replaced "createoodoc" by "oobuild" in the examples 1.205 2004-09-20 - Fixed a bug in createImageElement(); this could prevent the user from inserting images in presentation or drawing documents 1.204 2004-09-07 - Fixed a MSWin32 issue that could cause failures in the ooCreateFile() method (File.pm) - thanks to crazyinsomniac[at]yahoo.com 1.203 2004-08-03 - Fixed a bug that prevented the user from using custom templates (File.pm) - Removed Archive::Zip version control (due to a numbering anomaly in the PPM/Windows Archive::Zip distribution) - Downgraded File::Temp version control from 1.14 to 1.12 (that is the current PPM/Windows version) - Documentation update 1.202 2004-07-30 - Removing an unneeded warning when no colormap is loaded 1.201 2004-07-30 - Many changes in File.pm to allow the creation of new documents - Added many template files for document creation - Added Manifest.pm and Manifest.pod - Added contentClass() and getRootElement() in XPath.pm - Added readConfig() function in OODoc.pm and config.xml file - Added date conversion functions from OOo to time() format in Meta.pm - Added contentClass() in XPath.pm - Added oo2rgb(), rgb2oo(), ooLoadColorMap() in Styles.pm - Changed createImageElement() in Image.pm, allowing easy use in presentation and drawing documents - Included Time::Local and File::Temp in the PREREQ_PM - Installation procedure is now parametrizable (Makefile.PL) - Small changes in the test scripts - Added an example and update some other ones - Documentation update - Created OODoc.pod; removed POD from OODoc.pm 1.111 2004-07-11 - New method to generate unique names for temporary files (File.pm) due to an incompatibility with Archive::Zip 1.12 - Added write permission check in the working directory in the save() method (File.pm) - Added the workingDirectory() accessor in the main module 1.109 2004-07-08 - Removed redundant "use" statements in a test script - Small documentation update; no change in the code 1.108 2004-07-08 - Small documentation update; no change in the code 1.107 2004-07-06 - Fixed 2 bugs in the setSpan() method (Text.pm) - Fixed an encoding issue in a search/replace low level routine (XPath.pm) - Added getTopParagraph() method (Text.pm); this method was previously documented but not implemented - Added ooDecodeText() and ooEncodeText() in the main module - Minor corrections (again) in the documentation - Added 2 more examples 1.106 2004-05-27 - Minor fixes in POD files; no change in the code 1.105 2004-05-26 - Fixed some local encoding issues in XPath.pm - Added the extendText() method in XPath.pm - Fixed a style management bug in Text.pm (appendItem method - Added localEncoding() accessor in OODoc.pm - Test update - Added examples/TODO about the localisation of the examples - Documentation update 1.104 2004-03-12 - Added most of the Reference Manual (converted from OOo to POD) - Added a POD version of the README (Intro.pod) - Added a new example - Reorganised some in line comments in POD - Included Licence (EN & FR) in the package 1.103 2004-03-09 - Added comments - Added examples - Added more installation tests 1.102 2004-03-03 - Initial CPAN distribution OpenOffice-OODoc-2.125/OODoc/0000755000175000017500000000000011416644377013575 5ustar jmgjmgOpenOffice-OODoc-2.125/OODoc/Styles.pod0000644000175000017500000016675011345670342015573 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::Styles - Document styles and layout processing =head1 DESCRIPTION This class is designed to handle styles, whether automatic or named, contained in styles.xml or content.xml. It inherits from the common OpenOffice::OODoc::XPath class and brings style-focused features. This class should not be explicitly used in an ordinary application, because all its features are available in the OpenOffice::OODoc::Document class, in combination with other features. Practically, the present manual is provided to describe the style processing features of OpenOffice::OODoc::Document (knowing that these features are technically supported by the OpenOffice::OODoc::Styles component of the API). Remember that named styles are those that the end user can see and edit using through the GUI of an interactive office software (for ex. the Stylist tool in OpenOffice.org). Such styles usually have meaningful names and are stored in the styles.xml member. But an OpenDocument-compliant style may own two names, so-called 'name' and 'display-name'. The 'display-name' is the name as it's displayed by the office software, while the 'name' is the main identifier. Both are displayable character strings, but they often differ. For a given 'display-name', the application software is allowed to set any arbitrary 'name'. For example, with OpenOffice.org 2, the well-known pre-defined style whose display name is "Text body" is named "Text_20_body" (the space character is replaced by its hexadecimal value between two "_" characters). In the other hand, the 'name' and the 'display-name' generally don't differ when they contain letters and/or digits only. Remember that the 'name' (and not the 'display-name') is the main identifier of a style element. So, such a method as getStyleElement("style name") uses the 'name' attribute to retrieve a style descriptor and, in case of failure, it attempts to retrieve the same element by 'display-name' (unless you change this behaviour through the 'retrieve_by' document property). Care should be taken particularly with predefined base styles in OpenOffice.org. These styles are described in styles.xml just like named styles, but they appear to the end user with localised names (in their local language), so the really displayed style name is neither the 'name' nor the 'display-name' stored attributes. For example, in the French distribution of OpenOffice.org, the "Text body" style appears as "Corps de texte", while its "display-name" is "Text body" and its "name" is "Text_20_body". This localization is hard-coded in the office software for a few predefined styles, and it's not stored in the file. However, this is not a problem for user-defined styles as the stored display-name is exactly the same as the effective display name. There are also numerous "automatic" styles in a document which are created implicitly by the office application each time a particular set of presentation attributes is given to an element, but where no named style is referenced. Automatic styles which apply to the document body are stored in content.xml (but in an XML element isolated from the content). An automatic style's name can change randomly each time the document is edited or saved through an interactive desktop application. Applications which access automatic styles will not want to indicate them using "hard-coded" names. The best way is to retrieve each automatic style via an object that is known to use it. Using a "hard-coded" name is all right for styles created by a program (the createStyle() method requires it), but such a name should only be considered to be stable for the duration of the session. If you want a program-created style name to be then respected by OpenOffice.org, you must create it as a named style. This is no more complicated, but it is better to avoid making hundreds of styles visible to the user that they do not need to see. There are some structural differences between the old OpenOffice.org 1.0 format and the OASIS OpenDocument (ODF) one. A few of these differences aren't made fully transparent by OpenOffice::OODoc. So, in some cases, a program including style definitions or updates doesn't produce exactly the same results with both OOo 1 and ODF documents. The page styles are more complex than the other usual styles. A page style Some styles are more complex than others as they describe the page layout. so called "master page", can actually define a header, a footer, margins, and a background. Page headers and footers can contain text and images; as a consequence, some of their features can be handled by OODoc::Text and OODoc::Image. A background contains a colour and can also include a background image (several methods are possible). Presentation of these objects is itself controlled by styles. All of this leads to the conclusion that it is not enough just to associate each content element with a style. In reality, document styles form a rather complex network of interdependencies. As for page styles, the OpenDocument format contains a concept which must be understood in order to use some of the following methods. By virtue of the principle of separation of content and presentation, the definition of a page style is based on two distinct objects: "master page" and "page layout". A "master page" object encompasses any page style content (i.e. the content of headers and footers) and links to a "page layout" object which describes page presentation characteristics (with large numbers of parameters from page dimensions to background colour to footnote separator size, etc.). Names which appear in the list of page styles in OpenOffice.org are actually names of "master pages". However, to work with physical aspects of the presentation, you have to access the associated "page layout". To complicate matters, there are also header and footer styles. Each object contained in a header or footer (e.g. paragraph or image) has a style. The number and range of styles are much larger that you would imagine just looking at the style management tool in any office software. Up to a point, OODoc::Styles methods make life easier for you by masking some of this complexity. In OODoc::Styles methods, styles are normally indicated by their logical names (which must be unique), but, except where otherwise stated, they can also be indicated by their style element reference as well. Moreover, when a method is expecting a page layout as an argument but the programmer passes it a master page instead (whether by design or by mistake), it "knows" in most cases how to automatically select the associated page layout. OODoc::Styles allows the applications to create new styles, and not only to update existing styles. However, defining a style requires a great many attributes. Some appear in code examples in this manual, but for a full list of possible attributes for each style, you must refer to the OpenDocument specification. As a consequence, building styles from scratch by program is not a recommended practice. It's much more easy to create documents which all the needed styles through an ODF-compliant office software, and to use them as templates in the programs, knowing that it's very easy to retrieve an existing style, to copy it and to re-use it (as is or customised) in new documents. OODoc::Styles module is designed to allow applications to manipulate any style and even create new ones. It is not recommended, however, to use it to create a presentation entirely from code. Here again, it is better to start from document templates which already contain at least a blank of each required style. =head2 Methods =head3 Constructor : OpenOffice::OODoc::Styles->new() Short form: odfStyles() This constructor should not be explicitly used in ordinary applications knowing that all the features of the returned object are inherited by any Document object. See OpenOffice::OODoc::XPath->new() for common arguments. Returns an OODoc::XPath OpenDocument connector with additional style-aware features. The member loaded by default is "styles.xml" which gives access to named or automatic styles associated with the page layout. The "content.xml" part should be forced if the application is to work with styles associated with the document body (automatic styles only). =head3 backgroundImageLink(page [, link]) Allows you to check the background image's link (if found) for the page style given as the first argument. If another link is given as the second argument, it replaces the existing link. See imageLink in OODoc::Image about links. Put more simply, a link is the address of the graphics file which corresponds to the physical content of the image. Even though the background image belongs to the "page layout", the first argument can also be either a "master page" or a "page layout". If the second argument "link" is given, its value replaces the existing link in the same way as with imageLink. Example: $doc->backgroundImageLink ("Standard", "http://www.genicorp.com/back.jpg"); If the page did not have a background image before the call, one is created. It must, however, be an external linked image (as in the above example), unless the link represents the internal address of an already loaded image. This method does not itself carry out any physical import of an image. See also importBackgroundImage. =head3 createMasterPage(name, options) Creates a new page style. Options are: 'layout' => page layout name 'next' => next master page style name The association with a "page layout" allows you to associate a layout to the page. Otherwise the page will have a default layout. Example: $doc->createMasterPage ( "MyPageStyle", layout => 'pm1', next => 'Standard' ); See the OpenDocument specification (or the 'Organizer' tab in the "Format/Page" dialog box of OpenOffice.org) if you want to know what a "Next Style" is. The optional "next" parameter simply gives the name of a "master page", which can be the one you are currently creating. =head3 createPageLayout(name, options) Creates a new layout style (page layout) which can be used by a page style (master page). Options are the same as for updatePageLayout(). =head3 createPageMaster(name, options) See createPageLayout() =head3 createStyle(name, options) Creates a new style of any type or class (depending on options) and returns its reference if successful. The first argument indicates the new style name which must be unique in the document. By default, there is no automatic uniqueness check. However, if a 'check' option is set to 'true', the method fails and produces an error message warning if the style already exists. If the external name of the style, as it could be made visible for the end-user through an OpenDocument-compliant editing software (such as OpenOffice.org), is not the same as the internal name, it may be set through a 'display-name' option. Without this option, the display name is the same as the internal name. If the active OODoc::Styles object is associated with a document content (content.xml), the new style is always taken to be an automatic style. If associated with the styles.xml part, the new style is considered to be a named style by default. However, the category => 'automatic' option (or category => 'auto') allows you to specify it as an automatic style. Please note: in the case of content.xml, the "category" option is ignored as all styles are automatic in this member. By default, the method stores a style in the form of an XML element "style:style" (which corresponds to the most commonly used content styles). Some style elements are indicated in a different way. The "namespace" and "type" options are available for this. If, for example, you want to create a notes configuration style (called "text:notes-configuration" in the ODF specification), you will have to specify the "notes-configuration" type explicitly in the "text" namespace using one of the following two options: namespace => 'text', type => 'notes-configuration' The possible options are: namespace => namespace type => style type family => style family (text, paragraph, ...) class => style class parent => parent style (inherit) next => next style If other style "organisation" attributes (often for links to other styles) prove to be needed but are not on the above list, they must be grouped together in a hash provided by the application and indicated by a "references" option. Of course, if you create a new style, you do not just specify it into terms of type, class or family, etc. You attribute its own presentation attributes which can be inherited by other styles which cite it as "parent". These personal attributes (whose nature obviously depends on the style type) are all attributed by the "properties" attribute which itself is a hash provided by the application. Here is an example of a paragraph style creation $doc->createStyle ( "Colour", family => 'paragraph', parent => 'Standard', properties => { 'fo:margin-left' => '2cm', 'fo:margin-right' => '1.5cm', 'fo:text-align' => 'justify', 'fo:background-color' => '#ffff00' } ); $doc->setStyle($doc->getParagraph(3), "P3"); This sequence gives paragraph 3 of the document a special style whose properties are given margins, text justification and a yellow background color (note that the ODF color codes are in RGB hexadecimal preceded by a '#', and 'ffff00' is the RGB value for for the yellow color). This is done using a style called "Colour" (reusable later for other paragraphs) based on the "Standard" style. The names of the properties can be found in the ODF specification (some of them come from the Form Object standard, so they begin with the "fo:" prefix). However, the given properties are related to the global layout of the paragraph. We could provide this new style with additional properties related to the text content of the paragraph. But, in a paragraph style definition, the "text" properties are not stored in the same logical area than the "paragraph" properties, and we can't set both in the same instruction. Fortunately, we can enrich any existing style at any time through the updateStyle() method: $doc->updateStyle ( "Colour", properties => { -area => 'text', 'style:font-name' => 'Times', 'fo:font-size' => '14pt', 'fo:font-weight' => 'bold', 'fo:font-style' => 'italic', 'fo:color' => '#000080' } ); This new sequence gives paragraph 3 (or any paragraph using the "Colour" style) a lovely Times font in dark blue size 14 bold italics. The '-area' parameter which appears in the 'properties' hash is not a property; it's a selector which instructs the API to select the "text" property set. Note: the hexadecimal color codes used in the example could be replaced by more user-friendly color names, according to a standard or application-specific RGB color table, through the odfColor() function introduced in the present manual chapter. If the '-area' selector is omitted, the property set whose name is the name of the style family (i.e. 'paragraph' in the last example). The '-area' selector is silently ignored when used with OOo 1 documents, and sometimes required for ODF, so you can safely use it if you want to write portable code. In addition, up to now, the unknown style attributes are simply ignored by the OpenOffice.org software, and they don't harm. However, if the document is later edited and saved through OpenOffice.org, every unknown attribute is removed. As a consequence, everybody can use proprietary (non-OpenDocument) style attributes for application-specific markup. Another example: $doc->createStyle ( "Photo1", family => 'graphics', parent => 'Graphics', properties => { 'style:vertical-pos' => 'from-top', 'style:horizontal-pos' => 'from-left', 'style:vertical-rel' => 'page', 'style:horizontal-rel' => 'page', 'draw:luminance' => '4%', 'draw:contrast' => '2%', 'draw:gamma' => '1.1', 'draw:transparency' => '5%', 'draw:red' => '-3%', 'draw:green' => '2%' } ); The "Photo1" style defined above is of course an image style i.e. in the "graphics" family, based on the parent graphics style "Graphics". Any images to which this style will be applied will have coordinates which relate to the upper left edge of the page measured from top to bottom and left to right. They will be presented with an increase in luminosity of 4% and contrast of 2%, gamma correction of 1.1 and 5% transparency. Moreover, 3% less red and 2% more green will freshen the image and highlight the vibrancy of the chlorophyll. There are yet more in the list of options. Note: In the given examples, "namespace" and "type" are not specified because the default namespace and type are appropriate here. (Rest assured that this is often the case when working with text styles.) So, while OpenOffice::OODoc supports both OOo 1 and ODF with the same API, the present version can't completely hide the differences between the two formats. However, the program's logic can hide these differences for the end-user, because it can know the format of the current document (see isOpenDocument in OpenOffice::OODoc::XPath). Defining a style can be made a lot easier by reusing an already existing style than by creating it programmatically. The simplest way is by inheritance using the "parent" option, but the link to "parent" creates a permanent dependency (any further changes in the parent will affect the children). OODoc::Styles offers another possibility: copy the properties of an existing style, without creating a link to a parent, using the "prototype" option. This option points to another style whose properties are then taken up and combined with the new properties. New properties prevail over old ones if the new properties replace existing attributes in the prototype style, just like when they are inherited. But there is no persistent link from the new style to its prototype. Example: $doc->createStyle ( "Bigger", prototype => "Colour", properties => { 'fo:font-size' => '16pt' } ); This new style called "Bigger" is an exact copy of the previously defined "Colour" style, except for the font size. The given "parent" style is not necessarily defined yet and, if the current document part is "content", it can be the name of a style defined in the "styles" member. (See getAncestorStyle() and getParentStyle()). Generally speaking, explicit parameters passed by an application (e.g. font size) prevail over prototype's parameters. The prototype parameter can be a style name (as in the above example) or a style element. If it's an element, its origin doesn't matter (it can be a copy of a style element previously extracted from another document). If it's a name, the prototype style is retrieved either in the current document (default) or, if the 'source' option is provided, in another document. The value of the 'source' option is another OODoc::Styles (or OODoc::Document) object. If this option is provided, createStyle looks in the indicated document for the prototype style. If 'source' is provided without 'prototype', the prototype style is supposed to have the same name as the style to be created. If you want to create a style called "MyStyle", for example, in document $doc1 which imitates a style called "HisStyle" in document $doc2 (where both documents are OODoc::Styles or OODoc::Document objects), you can do the following: $doc1->createStyle ( "MyStyle", prototype => "HisStyle", source => $doc2 ); but if you write $doc1-createStyle ( "MyStyle", source => $doc2 ); the local style "MyStyle" is built as a copy of the so-named style in the source document (it's a direct import). Whatever the origin of the prototype style, any property can be set or redefined in the new style. Not only can you import styles available in other documents, but you can also create automatic styles in a 'content' part which are derived from named styles found in the 'styles' member and vice-versa. WARNING: The "prototype" option can produce unexpected results if the two documents are not in the same format. As a consequence, using an OOo 1 style as the prototype of an OpenDocument one (and vice-versa) should be avoided. Always be careful of dependencies. There are often dependencies between styles. An application must be wary of importing styles with directly or indirectl dependencies on other styles which will not be available in the target document. Text styles are fairly easy to control in this way, but table, page and graphic styles, for example, have more complex dependencies. When a font name is set (generally through a 'style:font-name' text property) in a new style, take care of the availability of the corresponding font declaration in the document. A font is not rendered if it's not declared (see importFontDeclaration()). See the OpenDocument specification (chapter 14, "Styles") for a complete list of possible attributes for each type of style. However, creating sophisticated styles from scratch is *not* recommended; remember the most easy (and the less error-prone) way consists of creating template documents through the OpenOffice.org GUI (or any other ODF-compliant office software) and using them as style libraries. =head3 exportBackgroundImage(page [, destination]) Exports the graphics file which corresponds to the background image of a page style where the image exists and is internal to the OpenOffice.org archive. (A linked image is obviously not exportable since it is not actually present in the document.) See the exportImage method in OODoc::Image for export details. Example: $doc->exportBackgroundImage ("First Page", "C:\Images\backgrnd.jpg"); =head3 getAncestorStyle(style) Returns the name of the primary known ancestor of the given style. If the style has a standalone definition (i.e. it's its own ancestor), the method returns it's own name. This method returns the ancestor name as it's known in the current document space. The genealogy is not followed out of the scope of the current XML part. For example, if we have an automatic paragraph style "P1", defined in the "content" member and derived from "Text body", the returned ancestor name will be "Text body". However, "Text body" itself could be a derivative of "Standard". But "Text body" is defined in the "styles" member, so its definition (including the name of its parent style) is out of the scope. As a consequence, in a regular ODF document, there are 2 possible situations: - if the current space is "styles", the returned style name is really the name of the primary ancestor, because a style defined in this space can't inherit from anything elsewhere; - if the current space is "content", the returned value can be the name of a style defined elsewhere. A possible check is a simple call to getStyleElement() with the returned ancestor name. If getStyleElement() returns undef, then the ancestor style is not defined in the current space (and, if needed, we could reach it the "styles" member, if we currently work with the "content" member). Beware: the returned name is the main name (identifier), and not the display name. See also getParentStyle(). =head3 getAutoStyleList([options]) Returns a list of automatic style elements in the current document. By default, only "style" type elements in the "style" namespace are returned. You can select special styles using the "namespace" and "type" options. For example, if you want to get a list of number styles (namespace "number", type "number-style"), do it like this: my @styles = $doc->getAutoStyleList (namespace => 'number', type => 'number-style'); =head3 getAutoStyleRoot() Returns the element that contains all the automatic style elements. See also getNamedStyleRoot(). =head3 getBackgroundImageAttributes(page) Returns the attributes of the given page style's background image (if any), in the form of a hash (attribute => value). =head3 getBackgroundImageElement(page) Returns the element reference of the given page style's background image (if found). =head3 getDefaultStyleAttributes(default_style) Returns the given default style's attributes (if any). Default styles are generally "paragraph" and "graphics". See also updateDefaultStyle(). =head3 getDefaultStyleElement(family) Returns the default style element's reference given by its logical name. A default style describes default values assigned to certain attributes of a given style family. For example, to get the default paragraph style of a document, use: my $def_para = $doc->getDefaultStyleElement("paragraph"); =head3 getFontDeclaration(fontname) Returns the font declaration element corresponding to the given font name, or undef if the font is not declared in the current document. Example: unless ($doc->getFontDeclaration("Times New Roman")) { $doc->importFontDeclaration($doc2, "Times New Roman"); } See also importFontDeclaration(). =head3 getFontDeclarations() Returns the full list of the font declaration elements present in the document. The following example prints every declared font name: foreach my $fd ($doc->getFontDeclarations) { print $doc->getFontName($fd) . "\n"; } =head3 getFontName(font_decl) Returns the name of the given font declaration element. Returns the argument as is if this argument is the name of a declared font, or undef if the name is unknown. =head3 getFooterParagraph(masterpage, number) In a text document, returns a footer paragraph's reference, if the master page has a footer and the paragraph exists. Arguments are master page and paragraph number. Caution: the first argument can't be a page number, knowing that printable pages are dynamically created by the office software and don't exist in the stored document. =head3 getHeaderParagraph(masterpage, number) Like getFooterParagraph, but for a header. =head3 getMasterPageElement(page) Returns a master page element reference whose logical name is given, or undef if the page style is not found. You can also pass an element reference instead of a name. In this case, the method's role is simply to check if the element is indeed a master page type. If so, it returns the argument as is. If not, it returns undef. Look at the DESCRIPTION part of the present manual chapter for a few explanations about master pages (and, of course, feel free to dig in the OpenDocument specification for details). This method should preferently be used on the 'styles' member; it doesn't generally make sense with the 'content' member, knowing that the master pages are generally described as named styles. =head3 getMasterStyleList([options]) Returns a list of master styles in the current document. By default, the list contains the master page elements. Other kinds of styles may be retrieved, according to the 'namespace' and/or 'type' options (see getStyleElement()). But the search space is limited to the master styles area, whatever the type and the namespace. Like getMasterPageElement(), this method makes more sense on 'styles' members than on 'content' ones. =head3 getMasterStyleRoot() Like getNamedStyleRoot(), but the returned element contains the master style descriptors instead. =head3 getNamedStyleList([options]) Returns a list of named styles in the current document, using the same options as for getAutoStyleList. By definition, in OpenOffice.org documents this list should be empty in all elements except styles.xml. =head3 getNamedStyleRoot() Returns the root element of the named styles area. In other words, this method retrieves the element that contains all the named style elements, with the exception of the master styles. This element could be, for example, copied from a document to another one in order to use exactly the same named styles (user-defined or provided with the office software) in both. =head3 getOutlineStyleElement(level) Returns the outline style descriptor related to the given outline level. The returned element is available for subsequent get/set operations using getAttributes(), setAttributes(), and so on. See also updateOutlineStyle(). =head3 getPageLayoutAttributes(page) Returns the description of a page layout. The argument can be either a page layout directly or a master page style which refers to it. The structure of returned data is a hash of hashes. It contains four elements, each of which is a hash. As follows: - "references": style reference attributes with at least its name and possibly its links to other styles. - "properties": background description (dimensions, orientation, margins, colour, etc.). - "header": presentation attributes for the header. - "footer": presentation attributes for the footer. - "footnote-sep": footnote separator attributes. - "background-image": background image attributes. Attributes are displayed according to OpenOffice.org specifications. =head3 getPageMasterAttributes(page) See getPageLayoutAttributes() =head3 getPageLayoutElement(page) Returns the page layout element reference from a search argument which can be either a logical name or a page style reference. If the argument is a master page, the method returns the corresponding page master. =head3 getPageMasterElement(page) See getPageLayoutElement() =head3 getParentStyle(style) Returns the name of the parent of the given style, or undef if the style has a standalone definition (without inheritance). The returned name, if any, is the identifier of the parent style, which can differ from its display name. The returned parent name can be the name of a style defined elsewhere (or not defined yet). See also getAncestorStyle(). =head3 getStyleAttributes(style) Returns a style's description (other than a page style) given as a logical name or reference. The structure of returned data is a hash of hashes. It contains the two following elements: - "references": style reference attributes with at least its name and possibly its links to other styles (either its family, parent style, class and/or next style). - "properties": description of the presentation characteristics for this style (and which depend on the type of object the style is applied to). Remember that this structure can be used directly by an application to create or update another style. =head3 getStyleElement(style_name [, options]) Returns a style element's reference using its name, or undef if no style owns the given name in the current context. Remember that a style can be used in the 'content' context while its descriptor (i.e. the style element) is defined in either the 'styles' context or the 'content' one. If the application doesn't know where the needed style is defined, it must call getStyleElement() in both. If the first argument is already an element reference, it returns the argument if it is indeed a style element, and undef if not, so this method could be used in order to check if a given element is a style element or not. By default, the style name is sought amongst "style" type elements in the "style" namespace. If an application is looking for a special style (e.g. page or number), then it can pass the optional parameters "namespace" and/or "type". See the section on createStyle for these concepts. A search is of course limited to automatic styles if the current XML document is "content". If the document is "styles", the search for the name is made in all styles by default. You can, however, limit it with the "path" parameter where "path" equals "auto" to search in automatic styles or "named" in named styles. The name is a mandatory property, and the main identifier of any style in ODF-compliant documents. But an additional property, so-called 'display-name', is sometimes provided by the applications. The 'display-name' property, if provided, is made visible for the end user by the office software (for example in the stylist box of OpenOffice.org) while the primary name is hidden. By default, getStyleElement() looks for a style whose primary name matches the given name, then, if the query fails, it tries to retrieve the style according to its display name. However, if the "retrieve_by" property of the connector is set to 'display-name', the display name becomes the preferred identifier. If a "retry" parameter is provided and set to "false" or any other value than "1" or "true", no double query is done. In other words, if the first query (by primary name or by display name, according to the value of the "retrieve_by" property of the connector) fails, the method returns immediately without trying any other query. The default value is "1" (true). You should set it to "0" or "false" in order to save some computation time if, for example, your application doesn't need to take care of the possible differences between display names and internal names. =head3 getStyleList([options]) Combines the results of getAutoStyleList and getNamedStyleList (same options). =head3 importBackgroundImage(page, filename [, link]) Imports a background image into the given page style from an external file. The page style can be either a page layout or a master page. An optional link can be inserted (e.g. to reuse an existing link). See backgroundImageLink or imageLink (in OODoc::Image) for information about links. Otherwise, an internal link under "Pictures/" is created by default and takes the name of the source file. Returns the link if found, undef if not. Caution: the actual import is not made until a save is called (see importImage in OODoc::Image). =head3 importFontDeclaration(doc, fontname) =head3 importFontDeclaration(xml_string) In the first form, retrieves a font declaration in another document and installs it in the current document. The first argument is a OODoc::Styles or OODoc::Document object. Example: my $source = odfDocument ( file => "source.odt", part => "styles" ); my $target = odfDocument ( file => "target.odt", part => "styles" ); $target->importFontDeclaration($source, "Helvetica"); In the second form, the single argument is the XML string containing a font declaration. The following example creates a declaration for the "Comic Sans MS" font in an OpenDocument: $doc->importFontDeclaration ( '' ); This last import feature is not mainly provided in order to encourage raw XML coding! Be careful, the XML font declaration syntax is not exactly the same with the two supported document formats. This feature should be used in order to import previously exported font declarations (see exportXMLElement in OODoc::XPath). A font declaration must be imported if it's used in a newly created style and not currently available in the target document. =head3 masterPageExtension(page, extension_type [, element]) This method allows the user to get or set an extension to an existing master page. The most used extensions are "header", "footer", "header-left", "footer-left", but any other key could be provided (warning: there is no ODF-compliance check, so any application- specific tag is allowed, knowing that any provided keyword will be automatically prefixed by "style:" in the generated XML). See masterPageFooter(), masterPageFooterLeft(), masterPageHeader(), masterPageHeaderLeft(); these methods can be regarded as synonyms for masterPageExtension() with the four listed extension types. =head3 masterPageFooter(page [, element]) Returns the given page style's footer element reference (master page) or undef if not found. If the second argument is a content element, it is added to the footer. If the footer does not exist, it is created. =head3 masterPageFooterLeft(page [, element]) Returns the given page style's footer left element reference (master page) or undef if not found. A "footer left" element can be used to specify different content for left pages, if appropriate. Unless a footer left element is defined in the master page, the content of the footers on left and right pages is the same. If the second argument is a content element, it is added to the footer. If the footer does not exist, it is created. =head3 masterPageHeader(page [, element]) Returns the given page style's header element reference (master page) or undef if not found. If the second argument is a content element, it is added to the header. If the header does not exist, it is created. =head3 masterPageHeaderLeft(page [, element]) Returns the given page style's header left element reference (master page) or undef if not found. A "header left" element can be used to specify different content for left pages, if appropriate. Unless a header left element is defined in the master page, the content of the headers on left and right pages is the same. If the second argument is a content element, it is added to the header. If the header does not exist, it is created. =head3 pageLayout(master_page [, page_master]) Returns or modifies the layout of a given page style (master page). If the second argument is given, it replaces the old page layout value (i.e. it changes the layout of the page without changing the header or footer content. =head3 pageMasterStyle(master_page [, page_master]) See pageLayout() =head3 removeStyleElement(style [, options]) Deletes the given style. The argument and options are the same as for getStyleElement. The method returns "True" (1) if successful or undef if the style is not found. =head3 selectStyleElementByFamily(family [, options]) Returns the first (or only) available style in the given family (using the "family" attribute), or undef if not found. Options are the same as for getStyleElement. Example: my $style = $doc->selectStyleElementByFamily ( "graphics", type => 'default-style' ); selects the element which describes the default graphic style. This method is useful for selecting styles whose "family" attribute is their identifier (and which do not have a "name" attribute). For example, this is the case for default styles where there is normally a default style for the "paragraph" family and another for the "graphics" family. In the above example, we used the "type" option where the type is "default-style" and not "style". We did not use the "namespace" option because it would be pointless to know that the default style namespace is just the default namespace ("style"). =head3 selectStyleElementsByFamily(family [, options]) Like selectStyleElementByFamily but returns a list of elements which belong to the given family. The "family" argument is treated as a regular expression, so an application must therefore give the appropriate meta-characters if the search is to be limited to the exact family name. =head3 selectStyleElementsByName(name [, options]) Returns a list of styles whose names match the first argument (which is treated as a regular expression). Options are the same as for the other selectStyleElementsXXX methods. =head3 setBackgroundImage(page, options) Inserts or replaces a background image in a page style. The "page" argument points either to the page layout directly, or to the master page to which it refers. Options point to the graphics object and how it is presented. The returned value is the created or modified background image's element reference (see getBackgroundImageElement). You should first indicate the graphics file which contains the image and whether it will merely be linked to the page by reference, or if it has to be physically imported into the OpenOffice.org file. To "link" the image, you supply its address using the "link" option. To import it, you supply the image using the "import" option. Examples: $doc->setBackgroundImage ( "First page", import => "C:\Images\Logo.jpg" ); $doc->setBackgroundImage ( "First page", link => "C:\Images\Logo.jpg" ); These two calls produce the same effect, but the second only inserts a link to the image. Remember that if by error an application supplies both the "link" and "import" options, the "import" option is the one that prevails. The other options control the import of images as backgrounds. By default, OODoc::Styles installs the image in the center without tiling and with an automatic update-on-load attribute if the image is by external link. You can choose other options using the OpenOffice.org standard vocabulary. To link a background image which is stretched to fit the entire page, use the following: $doc->setBackgoundImage ( "First page", link => "C:\Images\back.jpg", 'style:repeat' => 'stretch' ); =head3 styleName(style_element [, name]) =head3 styleName(name [, options]) The first form checks that the given argument is indeed a "style" element reference and, if it is, returns its name (undef if not). If a name is given as the second argument, it replaces the style name. In the second form, the current style name is given. In this case, and without any other arguments, the method only checks if the given name is indeed a style and returns a positive result (undef if not). It is still possible to change its name using this form, by using the "newname" option. With this form, some other options allow you to choose the namespace, type and category (automatic or named). These options are "namespace", "type" and "category" (see getStyleElement for these concepts). Without these parameters, the default values are the same as for getStyleElement. Beware: the only recognized style name here is the main style name, which can differ from the display name. =head3 styleProperties(style [, options]) This method is for checking and updating the formatting properties of a given style. It is more limited than updateStyle, but easier to code. The styleProperties method accesses only the style's formatting attributes and does not touch its references, such as its name, class or family (see getStyleAttributes). With no options, the current style's properties are simply returned in the form of a hash in which the keys are attributes belonging to the OpenOffice.org standard vocabulary and which depend on the type of object. The same data structure can be used to modify a style's properties by passing options as a hash. This structure is the same as the sub-hash "properties" of getStyleAttributes or updateStyle. If you wanted to redo the style we called "Colour" (see createStyle), for example, changing the colour of the characters to red and replacing the italics with standard font, you could do it as follows: $doc->styleProperties ( "Colour", '-area' => 'text', 'fo:color' => odfColor("red"), 'fo:font-style' => undef ); This short sequence sets the "fo:color" attribute to red and clears the "font-style" attribute. Remember that in RGB notation, the quantity of red is given by the first two hexadecimal digits, which here are set to maximum, and by setting the green and blue to zero. The "font-style" attribute had previously been set to "italic". Here, the 'area' option is neutral if the document format is OOo, but it must be set to 'text' for an ODF document, because all that is related to characters belongs to the 'text' area in a paragraph style (see below). styleProperties returns all the style's properties but only modifies those that have been set using options. To clear an existing property without giving it a new value, you must pass the corresponding option giving it a null value. If the current document is an OASIS Open Document, an additional "-area" option should be provided, because a style's properties may be stored in logical parts. For example, in a paragraph style, some properties apply to the paragraph itself, while some other ones apply to its text content (and some text properties can have the same name as some paragraph properties). The default value is the name of the style family. For example, if the style family is "paragraph", the "paragraph" part is selected by default. Because it updates font attributes (that are text properties), the example above couldn't work against an Open Document without an additional "area" option with the appropriate value: $doc->styleProperties ( "Colour", '-area' => 'text', 'fo:color' => "#ff0000", 'fo:font-style' => undef ); After creating a new paragraph style in an Open Document, this method should be used in order to set the properties which have not been set by createStyle because of the separation in two areas. In the following example, the 'paragraph' properties are directly set with createStyle, then the 'text' properties are set with styleProperties: my $style = $doc->createStyle ( "CenteredStyle", family => 'paragraph', parent => 'Standard', properties => { '-area' => 'paragraph', 'fo:text-align' => 'center', 'fo:margin-left' => '0.5cm', 'fo:margin-right' => '0.5cm' } ); $doc->styleProperties ( $style, '-area' => 'text', 'fo:color' => oo2rgb("blue"), 'fo:font-weight' => 'bold', 'style:font-name' => 'Times New Roman' ); Note: According to the OASIS OpenDocument v1.0 specification, any arbitrary custom attribute could be created in anyone of the style's properties area, and *should* be preserved by conforming applications when editing the document. However, up to now, any custom property is lost as soon as the document is edited through OpenOffice.org. The "-area" option is silently ignored with OOo 1 documents. =head3 switchPageOrientation(page) Switches a portrait page to landscape and vice-versa. The argument is a page style (page layout or master page). 'portrait' and 'landscape' are not style properties. The logic of this method is very simplistic: it makes a swap between the height and the width of the page. CAUTION: don't try to give a page number as the argument. This method apply on a page style (i.e. master page) and not on a real page selected by its number. =head3 updateDefaultStyle(family, options) Modifies the default style for the given family according to an options hash given by the application. The family is generally "paragraph" or "graphics". Options are given according to the OpenOffice.org style attributes vocabulary. The following example shows how to change the font, font size and default tab stops in the text: $doc->updateDefaultStyle ( "paragraph", 'fo:font-name' => 'Helvetica', 'fo:font-size' => '10pt', 'style:tab-stop-distance' => '1.5cm' ); =head3 updateOutlineStyle(level, properties) =head3 updateOutlineStyle(outline style element, properties) Allows any change in the direct attributes of an outline style. The new properties must be provides through a hash, where each key is an OpenDocument-compliant attribute. The following example changes the numbering prefix and suffix, and the numbering format for the level 1 list elements, so their numbering will look like "[A] ", "[B] ", "[C] ", ... $doc->updateOutlineStyle ( 1, 'num-prefix' => "[", 'num-suffix' => "] ", 'num-format' => "A" ); See the OpenDocument specification for the full set of possible attributes. Any attribute provided without namespace prefix (i.e. not including a ':'), such as those in the example above, are automatically prefixed by 'style:'; other attributes must be provided with their prefixes. Caution, some outline presentation characteristics, such as bullet style, are not directly under the control of this element. They depend on children "style:*-properties" elements. =head3 updatePageLayout(page, options) Modifies all types of page presentation style characteristics (page master). The style given as the first argument can be either the appropriate page layout style directly, or a page style (master page) to which it refers. Options can be passed in the form of a hash of hashes (each option itself points to a hash containing the base attributes). The four top-level elements are as follows: references => name, family, etc. properties => global presentation attributes header => header presentation style footer => footer presentation style footnote-sep => footnote separator style background-image => backgrnd.jpg image characteristics The "references" branch will not generally be used unless you want to change the style's name. This data structure is the same as returned by getPageLayoutAttributes(). A combination of these two methods allows you to copy the characteristics of one page style to another easily, especially when you want to apply the page setup of one document to another. When you only want to modify an existing style however, you only need to specify the attributes which you want to change. A "prototype" option allows you to clone the characteristics of an existing page layout. This option can indicate either an existing page layout reference, its logical name, or even the reference or logical name of a master page which refers to it. Only the first method is supported if the prototype page layout belongs to another document. The style name is not replaced by the prototype style name. See also createStyle about using a prototype style. The following example shows the code required to change several properties of the "Right page" style i.e. top margin width, background colour, maximum footnote height, minimum header height and the colour and width of the footnote separator. $doc->updatePageLayout ( "Right page", properties => { 'fo:margin-top' => '2.5cm', 'fo:background-color => '#88eecc', 'style:footnote-max-height' => '3cm' }, 'footnote-sep' => { 'style:width' => '0.02cm', 'style:color' => '#0000ff' }, header => { 'fo:min-height' => '2cm' } ); Once again, it is better to start with a getPageLayoutAttributes() of an existing page than to create all your styles from code. =head3 updatePageMaster(page, options) See updatePageLayout() =head3 updateStyle(style, options) Modifies the characteristics of an existing style. Options are the same as for createStyle() except for "category", "namespace" and "type" which cannot be changed in an existing style since they form part of its basic identity. A style's logical name can, however, be changed. The first argument can be either a style name or a style element. The second way should be preferred when the program already owns the element (obtained, for example, through getStyleElement() or createStyle()). In the 'properties' structure, the 'area' switch is required with ODF (OOo 2) documents if the property area is not the default one (see styleProperties and createStyle about the 'area' option). You can use the "prototype" option to update a style with another style's characteristics, but this option does not replace the style's name with the prototype's name. Be careful, the "prototype" option doesn't work for any kind of style, and it's not recommended in this method. The best approach for replicating an existing style consists of creating a new style with the "prototype" option (see createStyle). By definition, the style already exists and can be indicated equally well by reference or by name. Returns the characteristics of the modified style, as in getStyleAttributes. =head2 Exported functions =head3 odfColor($red, $green, $blue) =head3 odfColor("$red,$green,$blue") =head3 odfColor($colorname) Converts an RGB or named colour in ODF-compliant hexadecimal format (6 digits after a leading '#'). The 1st form has the same effect as the rgb2hex() function of the Color::Rgb Perl module. The resulting value can be used to set any colour attribute in a style. In the first form, the 3 arguments are the conventional numeric RGB values (between 0 an 255). In the second form, the only one argument is a string containing 3 comma-separated RGB values. In the third form, the given string is the symbolic name of a colour (the name must be an existing one in the %COLORMAP hash). Example: ooLoadColorMap("D:\MyDocuments\Colors.txt"); $doc->styleProperties ( "HighColors", 'fo:color' => odfColor('black'), 'fo:background-color' => odfColor('yellow') ); If the argument seems to be already an hexadecimal RGB string (i.e. it begins by "#"), odfColor() checks it and returns it unchanged if it's a regular RGB value, or undef if not. Synonym: rgb2oo(). =head3 odfLoadColorMap($filename) Populates the %OpenOffice::OODoc::Styles::COLORMAP hash from the content of an RGB file. This file defines a colour dictionary. Without argument, the content of the $COLORMAP variable is considered as the filename. Each line must contain 4 space-separated fields. The 3 first fields represent, respectively, the red, green and blue values of a colour and must be positive integer values in the 0-255 range. The remainder of the line is considered as the symbolic name of a colour (it can contain spaces). Example: 144 238 144 light green 139 0 139 dark magenta 255 105 180 hot pink 255 99 71 tomato Such a file is sometimes provided in a system directory (for example /usr/lib/X11/rgb.txt in some Unix systems). In any case, the users can easily find and download it somewhere. For example, a convenient rgb.txt file is provided with the Color::Rgb Perl module (CPAN). When a COLORMAP is loaded, the programmer can provide symbolic, user- friendly names in place of RGB values to the odfColor() function. Without argument, the content of the $COLORMAP variable is considered as the filename. When the OpenOffice::OODoc::Styles module is loaded as a consequence of a "use OpenOffice::OODoc" statement, ooLoadColorMap() is automatically executed if a valid filename is provided in the element of the "OODoc/config.xml" file. =head3 oo2rgb($oocolor) Returns the conventional RGB value of an OOo-encoded colour. See rgbColor(). =head3 ooLoadColorMap($filename) Synonym of odfLoadColorMap(). =head3 rgb2oo($red, $green, $blue) =head3 rgb2oo("$red,$green,$blue") =head3 rgb2oo($colorname) See odfColor(). =head3 rgbColor(odf_color) Converts an ODF-color code into a decimal RGB code or, according to a mapping file, into a plain text conventional color name. In array context, returns a 3-element array containing the red, green, blue decimal values of the colour. In scalar context, returns either a string with concatenated, comma separated red, green, blue values, or, if these values exactly match a known colour (according to the current %COLORMAP), the corresponding symbolic name. This function can be used to display or compute separately the RGB values of any colour attribute of a style, or to export these values to an image processing software. It produces the same result as the hex2rgb() method of the Color::Rgb Perl module. rgbColor() is a synonym of oo2rgb(). =head2 Properties The 'retrieve_by' option, set to 'display-name', can be provided in order to use the display name instead of the primary name as the first style identifier. The %COLORMAP hash, defined as a class variable, contains a name to RGB translation table. When loaded, it allows the rgb2oo() function to use symbolic names in place of RGB values. The keys are symbolic, user-defined colour names, and the values are strings containing the concatenated, comma-separated RGB values. Example: %OpenOffice::OODoc::Styles::COLORMAP{'antique white'} = "250,235,215"; By default, this hash contains a short, arbitrary set of colour definitions such as 'red', 'green', 'blue', 'white', 'black' and a few others. The user can populate it from an external RGB file, through the ooLoadColorMap() function previously described, and/or through program instructions like the example above. =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2009 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/File.pod0000644000175000017500000004461211322442270015147 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::File - I/O operations with OpenDocument files =head1 DESCRIPTION The explicit use of this module is generally required only in programs which need to do some raw data import or export operations in OpenDocument files. This module can be used as a special wrapper of Archve::Zip. However, a look at the constructor and the save() methods is recommended, in order to get a better knowledge of the file interface which is used by the document-oriented modules (i.e. OODoc::XPath and its derivatives). If the program is only concerned with a single XML element from the file, it is unnecessary to create an OODoc::File explicitly. Just build an OODoc::XPath object with a filename as parameter. The XPath will create a "private" File itself and use it to access the data invisibly. Please note that OODoc::File is able to handle all standard zip archives and not just OpenOffice.org files. Filenames do not have to have a ".sx?" extension. It allows access by other modules, if required, to XML/OpenOffice data in compressed archives which are not necessarily in OpenOffice.org format. =head2 Methods =head3 Constructor : OpenOffice::OODoc::File->new(filename|handle) Short Forms (equivalent): odfFile(), odfContainer(), or odfPackage() Returns an instance of OODoc::File if the argument corresponds to a valid and accessible zip archive. The argument may be either a file path/name or an open (in binmode) and seekable IO::File. The file or file handle used here is used in order to initialize the ODF Connector for subsequent processing through the other submodules of the OpenOffice::OODoc API. It becomes the default target file if the data are updated and the changes committed using save(). If the OODoc::File object is loaded for update, it's strongly recommended to avoid the use of a single IO::File object to read and write. However, if the source is a file name, the same file name may be safely used as the source and the target (of course and as usual, any sensitive document must be backed up before processing). If the "create" option is set (see below), a file path/name may be provided, but a file handle must be avoided. See the explanations about the save() method below. Examples: my $container1 = odfContainer("doc1.odt"); my $fh = new IO::File "< doc2.odt"; binmode $fh; my $container2 = odfContainer($fh); An optional argument can be passed in hash format (key => value), after the filename. Like this: work_dir => "path" which designates the path to the XML working files also generated during a save for this object (each OpenOffice::OODoc::File object can have its own working directory); without this option, the working directory is set according to the content of the class variable $OpenOffice::OODoc::File::WORKING_DIRECTORY. Note: no content checking is carried out. The archive can be opened whether or not it is an OpenOffice.org document. It's possible to create an OpenOffice::OODoc::File object without providing an existing OpenOffice.org file. To do so, there is a special option: create => "class" where "class" is the document class according to the OpenOffice.org terminology, so it is one of the following values: "text", spreadsheet", "presentation", "drawing". These values are the same as the legal parameters of contentClass() in OpenOffice::OODoc::XPath. If "create" is provided, the first argument must not be a file handle, knowing that the target (if the data is later saved) can't be an open file, and that there is by definition no source file. Note that, for a creation, the value of the file argument is ignored so it may be undef or an empty string; however a valid path/name may be provided in order to provide a default target for a subsequent save(). For a very advanced use, it's possible to combine "create" with an additional option template_path => "path" to generate the new file from special, user-provided ODF templates instead of those included in the installation. If this option is not provided, the general template path (possibly changed with the templatePath() function) is used. The template path must indicate a directory which contains a set of ODF files whose names are "template.???", where "???" represents the conventional ODF and OOo suffixes, knowing that the supported suffixes are presently odt, ods, odp, odg, sxw, sxc, sxi, sxd. Of course, some applications don't need all the templates; for example, the "template.sx?" files should be omitted if the user don't want to use the legacy OpenOffice.org 1.0 format for new documents. When the "create" option is used, it's possible to provide an "opendocument" option in order to override the installation-level default file format for new documents. If this option is set to "1", "on" or "true", the new document will comply to the OASIS OpenDocument format; if it's set to "0", "off" or "false", the new document will be created according to the OpenOffice.org 1.0 format. The "opendocument" option is ignored without the "create" one (this tool is not a format converter for existing documents). Remember that the default format is set to OpenDocument in the CPAN distribution and the OpenOffice.org 1.0 format is deprecated. The returned object of new(), if successful, is a valid File object, whose methods listed below become available. If unsuccessful (generally due to non-existent file or invalid zip archive or even a corrupt zip archive), the constructor returns a null value (undef), and an error message is sent to the standard output. =head3 extract() Returns the decompressed content of the requested member, if contained in the archive and corresponds to an XML element of the currently active OpenOffice.org file instance. The parameter must therefore correspond to one of the members of the file (see Introduction). If the application uses any of the words "content", "meta", "styles" or "settings", in upper or lower case, the .xml extension is automatically added but any other names are accepted without change if they are indeed existing members of the archive. The following statements are equivalent: my $content = $archive->extract('META'); my $content = $archive->extract('meta.xml'); my $content = $archive->extract('meta); After the above calls, the variable $content contains the XML document which represents the metadata of an OpenOffice.org file. This content can be used, for example, to instance a Meta object. Note: in most "normal" cases, this method does not have to be called explicitly as it is called silently by each occurrence of XPath (therefore by Text and Meta which are derivatives of it), but only if XPath is constructed referencing an OODoc::File object as a parameter (see OODoc::XPath). An extract call is only useful when exporting the XML or handling it outside of OODoc::XPath. On error (e.g. unknown archive member), a null value is returned and an error message is produced. =head3 link() Connects a File object to an XPath object given as an argument. This connection has two output products: - immediately calls the extract method using the corresponding "specialist" component of the XPath object (metadata if OODoc::Meta, content if OODoc::Text, etc). - stores the link for later updates to all OpenOffice.org file members which may have been modified by XPath objects (in case a save is called, see below). Note: This method is used by OODoc::XPath to connect as "clients" to OODoc::File objects. It does not have to be called directly by highest-level programs which only use OODoc::XPath objects. =head3 raw_delete(member) Orders the deletion of any OpenOffice.org file member. Example: $archive->raw_delete("Pictures/100000AEFGH.jpg"); deletes the physical content of an image loaded in the file. It is entirely up to the application to ensure that such a deletion does not compromise the integrity of the file as no dependency checking is carried out here. In the above example, the delete operation could be particularly justified if the "image" member which referenced this content had been (or was going to be) otherwise removed, or if it had been replaced by an external reference. This method can be used to remove any XML or non-XML member. It can be combined with raw_import() in order to effect a raw replacement of content without interpretation. Caution: this method should not be used for an XML member (content, style, meta, etc.) which is currently "active" (i.e. linked to an active OODoc::XPath instance), unless the member has been loaded as "read only" (search in the OpenOffice::OODoc::XPath for the "read_only" option). Note: calls to this method only prepare the deletion, which is actually carried out by the save() method if it occurs before the end of the program. If save() is called with a filename which is different from the source filename, the source file remains unchanged and the deleted member is simply not transferred to the target file. =head3 raw_export(member [, destination]) Decompresses and exports the physical content of a given member (XML or non-XML) of an archive. If the second argument is used, it passes the destination filename (perhaps with access path). If not, the file is exported using its internal archive name. Examples: $archive->raw_export("styles.xml"); exports the "styles.xml" member into a file of the same name in the current directory. $archive->raw_export("styles.xml", "/tmp/my_style.xml"); exports the same XML member to a given path. raw_export executes immediately (and is not deferred like raw_import). If successful, the returned value is the filename of the exported file. =head3 raw_import(member [, source]) Creates or replaces the indicated member by importing an external source file. If the second argument is omitted, the source file is taken to have the same access path as the internal member. Example: $arch1->raw_export("styles.xml", "/tmp/styles.xml"); $arch2->raw_import("styles.xml", "/tmp/styles.xml"); or, in more compact form: $arch2->raw_import ( "styles.xml", $arch1->raw_export("styles.xml") ); The above sequence requests the import of the member "styles.xml" from an archive called $arch1 into $arch2 (a direct means of using the styles and page layout of one document as a template for another). The imported files can be any type and have any content. This "raw" method treats an OpenOffice.org file as any other zip archive. It notably allows the import of non-XML members (images, sounds, programs, etc) which the application deals with (and which can be ignored by the office application). Caution: the import is only completed when a save() method is called by the importing object. It can only succeed if the source file is available at that very moment. A raw_import method can be called before the imported file is available (no check of availability is made). An error will be caused if the file is absent at the time of the save() call. If several raw_import statements are run against the same filename, there will actually be a corresponding number of copies of the file in its final state which are imported at the moment of the save() call, even if it had perhaps been modified in the meantime (probably not a very useful outcome). =head3 save([filename|handle]) Commits all the changes made in the members of the archive and makes the resulting content persistent. In addition, the external, non-XML resources (i.e. image files) that have previously been targeted by import methods, if any, are physically imported when save() is called (and not before). If these resources are not available at this time, they are ignored and a warning is issued for each missing file. Example: $archive->save("target.odt"); The target may be a file name (with optional path) or an IO::File object (that must be open, in binmode and writable). Without argument, save(), attempts to write the on the file or handle that was specified as the source file at the creation time, if any. However, if the OODoc::File was initialized with a IO::File, the use of the same IO::File for writing the output is presently not recommended; in some situations, bi-directional I/Os against an application-provided open IO::File may result in unpredictable results with the current implementation. Of course, if the OODoc::File object has been initialized with the "create" option and with an undef value or an empty string as file name, save() requires an explicit target (a valid file/path or an open and writable IO::File). Please note that OODoc::File does not check the content, and the save() method can be used to force through any data which may produce a file not compliant with the ODF packaging specification. The filename argument is optional. If it is omitted, the source file previously supplied by the constructor call (if any) is updated. Without a filename argument, save() if the source document was got through a IO::Handle. Even though the life of an OODoc::File object does not necessarily end with a save, it is recommended that you avoid repeated alternation between save and extract (the object's behaviour in this situation has not been tested). Normally it is preferable to call a save once and for all at the end of a series of updates. Only a call to OODoc::File's save() method saves content, metadate and presentation changes made by other OODoc components to the OpenOffice.org file, including raw imports of external data (raw_import). However, the XML members currently associated with "read only" OODoc::XPath objects are not changed in the file. No file is created or modified before this method is called, with the exception of external files created by raw_export. Nevertheless File's save can be called automatically and silently by an OODoc::XPath object but only where it has been called as a parameter explicitly for this purpose (see the chapter on OODoc::XPath). All XPath objects which are "connected" to a File object by link must be present at the time of the save call. If one of these objects has meanwhile been deleted, the consequences are unpredictable and, in any case, any document updates it could have made are lost. =head3 templatePath([path]) Class function (not to be used as a method). Can be replaced by odfTemplatePath(), see OODoc man page. Accessor to get/set the path for a user-defined set of ODF templates, to be used in case of new document creation. This path is empty by default. Without an explicit template path, the default XML templates provided with the OpenOffice::OODoc distribution are automatically selected. The template path must designate a directory containing 4 regular ODF files, each one corresponding to a supported ODF document class, i.e. "template.odt", "template.ods", "template.odp", "template.odg". However, the user don't need to provide templates that will not be used. Note that it's always possible to override the default template path when a new document is created, thanks to the 'template_path' option. =head2 Properties No class variables are exported. The class variable $OpenOffice::OODoc::File::WORKING_DIRECTORY indicates the directory to be used for temporary files (used but the save() method) when no object-specific path is provided through the 'work_dir' option. By default, the working directory is the current directory ('.'). The $OpenOffice::OODoc::File::TEMPLATE_PATH variable, empty by default, can contain an alternative path for document generation template files; it can be set with the templatePath() function. The $OpenOffice::OODoc::File::$DEFAULT_OFFICE_FORMAT variable, whose default is 2, controls the default format for newly created files (when the format is not explicitly selected by the application). Allowed values are "1" for OpenOffice.org 1.0 and "2" for OASIS OpenDocument. In a regular installation, this variable is automatically set according to the element of the config.xml file (see INSTALL). Instance hash variables are: 'linked' => list of connected OODoc::XPath instances 'members' => list of file member (*.xml and others) 'raw_members' => list of import files 'temporary_files' => created temporary files. Where $f is a given instance of OODoc::File, the table @{$f->{'linked'}} is a list of OODoc::XPath objects which were connected to $f by the link method and @{$f->{'members'}} is a list of members found in the archive when new is called. These variables can be read at any time even though they were normally designed to be used internally by OODoc::File. Unless you are just finding out exactly what they do, it is dangerous to modify them. Applications do not normally need to access them. =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2008 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/XPath.pm0000644000175000017500000025116511416643331015156 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : XPath.pm 2.237 2010-07-12 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2010 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- package OpenOffice::OODoc::XPath; use 5.008_000; use strict; our $VERSION = '2.237'; use XML::Twig 3.32; use Encode; require Exporter; our @ISA = qw ( Exporter ); our @EXPORT = qw ( TRUE FALSE is_true is_false odfLocaltime odfTimelocal ); #------------------------------------------------------------------------------ use constant { TRUE => 1, FALSE => 0 }; sub is_true { my $arg = shift or return FALSE; $arg = lc $arg; return ($arg eq '1' || $arg eq 'true' || $arg eq 'on') ? TRUE : FALSE; } sub is_not_true { return is_true(shift) ? FALSE : TRUE; } #------------------------------------------------------------------------------ BEGIN { *dispose = *DESTROY; *update = *save; *getXMLContent = *exportXMLContent; *getContent = *exportXMLContent; *getChildElementByName = *selectChildElementByName; *getElementByIdentifier = *selectElementByIdentifier; *blankSpaces = *spaces; *createSpaces = *spaces; *createTextNode = *newTextNode; *getFrame = *getFrameElement; *getUserFieldElement = *getUserField; *getVariableElement = *getVariable; *getNodeByXPath = *selectNodeByXPath; *getNodesByXPath = *selectNodesByXPath; *getElementList = *selectNodesByXPath; *isCalcDocument = *isSpreadsheet; *isDrawDocument = *isDrawing; *isImpressDocument = *isPresentation; *isWriterDocument = *isText; *odfVersion = *openDocumentVersion; } #------------------------------------------------------------------------------ our %XMLNAMES = # OODoc root element names ( 'content' => 'office:document-content', 'styles' => 'office:document-styles', 'meta' => 'office:document-meta', 'manifest' => 'manifest:manifest', 'settings' => 'office:document-settings' ); # characters to be escaped in XML our $CHARS_TO_ESCAPE = "\"<>'&"; # standard external character set our $LOCAL_CHARSET = 'utf8'; # standard ODF character set our $OO_CHARSET = 'utf8'; # default element identifier our $ELT_ID = 'text:id'; #------------------------------------------------------------------------------ # basic conversion between internal & printable encodings sub OpenOffice::OODoc::XPath::decode_text { return Encode::encode($LOCAL_CHARSET, shift); } sub OpenOffice::OODoc::XPath::encode_text { return Encode::decode($LOCAL_CHARSET, shift); } #------------------------------------------------------------------------------ # common date formatting functions sub odfLocaltime { my $time = shift || time(); my @t = localtime($time); return sprintf ( "%04d-%02d-%02dT%02d:%02d:%02d", $t[5] + 1900, $t[4] + 1, $t[3], $t[2], $t[1], $t[0] ); } sub odfTimelocal { require Time::Local; my $ootime = shift; return undef unless $ootime; $ootime =~ /(\d*)-(\d*)-(\d*)T(\d*):(\d*):(\d*)/; return Time::Local::timelocal($6, $5, $4, $3, $2 - 1, $1); } #------------------------------------------------------------------------------ # object coordinates, size, description control sub setObjectCoordinates { my $self = shift; my $element = shift or return undef; my ($x, $y) = @_; if ($x && ($x =~ /,/)) # X and Y are concatenated in a single string { $x =~ s/\s*//g; # remove the spaces $x =~ s/,(.*)//; $y = $1; # split on the comma } $x = '0cm' unless $x; $y = '0cm' unless $y; $x .= 'cm' unless $x =~ /[a-zA-Z]$/; $y .= 'cm' unless $y =~ /[a-zA-Z]$/; $self->setAttributes($element, 'svg:x' => $x, 'svg:y' => $y); return wantarray ? ($x, $y) : ($x . ',' . $y); } sub getObjectCoordinates { my $self = shift; my $element = shift or return undef; my $x = $element->getAttribute('svg:x'); my $y = $element->getAttribute('svg:y'); return undef unless defined $x and defined $y; return wantarray ? ($x, $y) : ($x . ',' . $y); } sub setObjectSize { my $self = shift; my $element = shift or return undef; my ($w, $h) = @_; if ($w && ($w =~ /,/)) # W and H are concatenated in a single string { $w =~ s/\s*//g; # remove the spaces $w =~ s/,(.*)//; $h = $1; # split on the comma } $w = '0cm' unless $w; $h = '0cm' unless $h; $w .= 'cm' unless $w =~ /[a-zA-Z]$/; $h .= 'cm' unless $h =~ /[a-zA-Z]$/; $self->setAttributes($element, 'svg:width' => $w, 'svg:height' => $h); return wantarray ? ($w, $h) : ($w . ',' . $h); } sub getObjectSize { my $self = shift; my $element = shift or return undef; my $w = $element->getAttribute('svg:width'); my $h = $element->getAttribute('svg:height'); return wantarray ? ($w, $h) : ($w . ',' . $h); } sub setObjectDescription { my $self = shift; my $element = shift or return undef; my $text = shift; my $desc = $element->first_child('svg:desc'); unless ($desc) { $self->appendElement($element, 'svg:desc', text => $text) if (defined $text); } else { if (defined $text) { $self->setText($desc, $text, @_); } else { $self->removeElement($desc, @_); } } return $desc; } sub getObjectDescription { my $self = shift; my $element = shift or return undef; return $self->getXPathValue($element, 'svg:desc'); } sub getObjectName { my $self = shift; my $element = shift or return undef; my $name = shift; my $attr = $element->getPrefix() . ':name' ; return $self->getAttribute($element, $attr); } sub setObjectName { my $self = shift; my $element = shift or return undef; my $name = shift; my $attr = $element->getPrefix() . ':name' ; return $self->setAttribute($element, $attr, @_); } sub objectName { my $self = shift; my $element = shift or return undef; my $name = shift; my $attr = $element->getPrefix() . ':name' ; return (defined $name) ? $self->setAttribute($element, $attr => $name) : $self->getAttribute($element, $attr); } #------------------------------------------------------------------------------ # basic element creation sub OpenOffice::OODoc::XPath::new_element { my $name = shift or return undef; return undef if ref $name; $name =~ s/^\s+//; $name =~ s/\s+$//; if ($name =~ /^parse($name, @_); } else # create element from name and optional data { return OpenOffice::OODoc::Element->new($name, @_); } } #------------------------------------------------------------------------------ # text node creation sub OpenOffice::OODoc::XPath::new_text_node { return OpenOffice::OODoc::XPath::new_element('#PCDATA', @_); } #------------------------------------------------------------------------------ # basic conversion between internal & printable encodings (object version) sub inputTextConversion { my $self = shift; my $text = shift; return undef unless defined $text; my $local_encoding = $self->{'local_encoding'} or return $text; return Encode::decode($local_encoding, $text); } sub outputTextConversion { my $self = shift; my $text = shift; return undef unless defined $text; my $local_encoding = $self->{'local_encoding'} or return $text; return Encode::encode($local_encoding, $text); } sub localEncoding { my $self = shift; my $encoding = shift; $self->{'local_encoding'} = $encoding if $encoding; return $self->{'local_encoding'} || ''; } sub noLocalEncoding { my $self = shift; delete $self->{'local_encoding'}; return 1; } #------------------------------------------------------------------------------ # search/replace text processing routine # if $replace is a user-provided routine, it's called back with # the current argument stack, plus the substring found sub _find_text { my $self = shift; my $text = shift; my $pattern = $self->inputTextConversion(shift); my $replace = shift; if (defined $pattern) { if (defined $replace) { if (ref $replace) { if ((ref $replace) eq 'CODE') { return undef unless ( $text =~ s/($pattern)/ { my $found = $1; Encode::_utf8_on($found) if Encode::is_utf8($text); my $result = &$replace(@_, $found); $result = $found unless (defined $result); $result; } /eg ); } else { return undef unless ($text =~ /$pattern/); } } else { my $r = $self->inputTextConversion($replace); return undef unless ($text =~ s/$pattern/$r/g); } } else { return undef unless ($text =~ /$pattern/); } } return $text; } #------------------------------------------------------------------------------ # search/replace content in descendant nodes sub _search_content { my $self = shift; my $node = shift or return undef; my $content = undef; if ($node->isTextNode) { my $text = $self->_find_text($node->text, @_); if (defined $text) { $node->set_text($text); $content = $text; } } else { foreach my $n ($node->getTextDescendants) { my $text = $self->_find_text($n->text, @_); if (defined $text) { $n->set_text($text); $content .= $text; } } } return $content; } #------------------------------------------------------------------------------ # is this an OASIS Open Document or an OpenOffice 1.x Document ? sub isOpenDocument { my $self = shift; my $root = $self->getRootElement; die __PACKAGE__ . " Missing root element\n" unless $root; my $ns = $root->att('xmlns:office'); return $ns && ($ns =~ /opendocument/) ? 1 : undef; } sub openDocumentVersion { my $self = shift; my $new_version = shift; my $root = $self->getRootElement or return undef; $root->set_att('office:version' => $new_version) if $new_version; return $root->att('office:version'); } #------------------------------------------------------------------------------ # document class check sub isContent { my $self = shift; return ($self->contentClass()) ? 1 : undef; } sub isSpreadsheet { my $self = shift; return ($self->contentClass() eq 'spreadsheet') ? 1 : undef; } sub isPresentation { my $self = shift; return ($self->contentClass() eq 'presentation') ? 1 : undef; } sub isDrawing { my $self = shift; return ($self->contentClass() eq 'drawing') ? 1 : undef; } sub isText { my $self = shift; return ($self->contentClass() eq 'text') ? 1 : undef; } #------------------------------------------------------------------------------ sub _get_container # get a new OODoc::File container { require OpenOffice::OODoc::File; my $doc = shift; return OpenOffice::OODoc::File->new ( $doc->{'file'}, create => $doc->{'create'}, opendocument => $doc->{'opendocument'}, template_path => $doc->{'template_path'} ); } sub _get_flat_file # get flat ODF content { my $doc = shift; my $source = $doc->{'file'}; $doc->{'xpath'} = UNIVERSAL::isa($source, 'IO::File') ? $doc->{'twig'}->safe_parse($source) : $doc->{'twig'}->safe_parsefile($source); return $doc->{'path'}; } sub new { my $caller = shift; my $class = ref($caller) || $caller; my $self = { auto_style_path => '//office:automatic-styles', master_style_path => '//office:master-styles', named_style_path => '//office:styles', image_container => 'draw:image', image_xpath => '//draw:image', image_fpath => '#Pictures/', local_encoding => $OpenOffice::OODoc::XPath::LOCAL_CHARSET, @_ }; foreach my $optk (keys %$self) { next unless $self->{$optk}; my $v = lc $self->{$optk}; $self->{$optk} = 0 if ($v =~ /^false$|^off$/); } $self->{'container'} = $self->{'file'} if defined $self->{'file'}; $self->{'container'} = $self->{'archive'} if defined $self->{'archive'}; $self->{'part'} = $self->{'member'} if $self->{'member'}; $self->{'part'} = 'content' unless $self->{'part'}; unless ($self->{'element'}) { my $m = lc $self->{'part'}; if ($m =~ /(^.*)\..*/) { $m = $1; } $self->{'element'} = $OpenOffice::OODoc::XPath::XMLNAMES{$m}; } # create the XML::Twig if (is_true($self->{'readable_XML'})) { $self->{'readable_XML'} = 'indented'; } $self->{'element'} = $OpenOffice::OODoc::XPath::XMLNAMES{'content'} unless $self->{'element'}; if ($self->{'element'}) { $self->{'twig'} = XML::Twig->new ( elt_class => "OpenOffice::OODoc::Element", twig_roots => { $self->{'element'} => 1 }, pretty_print => $self->{'readable_XML'}, %{$self->{'twig_options'}} ); } else { $self->{'twig'} = XML::Twig->new ( elt_class => "OpenOffice::OODoc::Element", pretty_print => $self->{'readable_XML'}, %{$self->{'twig_options'}} ); } # other OODoc::Xpath object $self->{'container'} = $self->{'container'}->{'container'} if ( ref($self->{container}) && $self->{'container'}->isa('OpenOffice::OODoc::XPath') ); if ($self->{'xml'}) # load from XML string { delete $self->{'container'}; delete $self->{'file'}; $self->{'xpath'} = $self->{'twig'}->safe_parse($self->{'xml'}); delete $self->{'xml'}; } elsif (defined $self->{'container'}) { delete $self->{'file'}; # existing OODoc::File object if ( UNIVERSAL::isa($self->{'container'}, 'OpenOffice::OODoc::File') ) { my $xml = $self->{'container'}->link($self); $self->{'xpath'} = $self->{'twig'}->safe_parse($xml); } # source file or filehandle else { $self->{'file'} = $self->{'container'}; delete $self->{'container'}; if ( $self->{'flat_xml'} || (lc $self->{'file'}) =~ /\.xml$/ ) # XML flat file { $self->{'xpath'} = _get_flat_file($self); } else { # new OODoc::File object $self->{'container'} = _get_container($self); return undef unless $self->{'container'}; delete $self->{'file'}; my $xml = $self->{'container'}->link($self); $self->{'xpath'} = $self->{'twig'}->safe_parse($xml); } } } unless ($self->{'xpath'}) { warn "[" . __PACKAGE__ . "::new] No ODF content\n"; return undef; } # XML content loaded & parsed bless $self, $class; $self->{'opendocument'} = $self->isOpenDocument; if ($self->{'opendocument'}) { $self->{'image_container'} = 'draw:frame'; $self->{'image_xpath'} = '//draw:frame'; $self->{'image_fpath'} = 'Pictures/'; } $self->{'member'} = $self->{'part'}; # for compatibility $self->{'archive'} = $self->{'container'}; # for compatibility $self->{'context'} = $self->getRoot; $self->{'body'} = $self->getBody; return $self; } #------------------------------------------------------------------------------ # destructor sub DESTROY { my $self = shift; if ($self->{'body'}) { $self->{'body'}->dispose(); } delete $self->{'body'}; if ($self->{'context'}) { $self->{'context'}->dispose(); } delete $self->{'context'}; if ($self->{'xpath'}) { $self->{'xpath'}->dispose(); } delete $self->{'xpath'}; if ($self->{'twig'}) { $self->{'twig'}->dispose(); } delete $self->{'twig'}; delete $self->{'xml'}; delete $self->{'content_class'}; delete $self->{'file'}; delete $self->{'container'}; delete $self->{'archive'}; delete $self->{'part'}; delete $self->{'twig_options'}; $self = {}; } #------------------------------------------------------------------------------ # get a reference to the embedded XML parser for share sub getXMLParser { warn "[" . __PACKAGE__ . "::getXMLParser] No longer implemented\n"; return undef; } #------------------------------------------------------------------------------ # make the changes persistent in an OpenOffice.org file sub save { my $self = shift; my $target = shift; my $filename = ($target) ? $target : $self->{'file'}; my $archive = $self->{'container'}; unless ($archive) { return undef if is_true($self->{'read_only'}); if ($filename) { open my $fh, ">:utf8", $filename; $self->exportXMLContent($fh); close $fh; return $filename; } else { warn "[" . __PACKAGE__ . "::save] Missing file\n"; return undef; } } $filename = $archive->{'source_file'} unless $filename; unless ($filename) { warn "[" . __PACKAGE__ . "::save] No target file\n"; return undef; } unless ($self->{'part'}) { warn "[" . __PACKAGE__ . "::save] Missing archive part name\n"; return undef; } my $result = $archive->save($filename); return $result; } #------------------------------------------------------------------------------ # raw file import sub raw_import { my $self = shift; if ($self->{'container'}) { my $target = shift; unless ($target) { warn "[" . __PACKAGE__ . "::raw_import] " . "No target member for import\n"; return undef; } $target =~ s/^#//; return $self->{'container'}->raw_import($target, @_); } else { warn "[" . __PACKAGE__ . "::raw_import] " . "No container for file import\n"; return undef; } } #------------------------------------------------------------------------------ # raw file export sub raw_export { my $self = shift; if ($self->{'container'}) { my $source = shift; unless ($source) { warn "[" . __PACKAGE__ . "::raw_import] " . "Missing source file name\n"; return undef; } $source =~ s/^#//; return $self->{'container'}->raw_export($source, @_); } else { warn "[" . __PACKAGE__ . "::raw_import] " . "No container for file export\n"; return undef; } } #------------------------------------------------------------------------------ # exports the whole content of the document as an XML string sub exportXMLContent { my $self = shift; my $target = shift; if ($target) { return $self->{'twig'}->print($target); } else { return $self->{'twig'}->sprint; } } #------------------------------------------------------------------------------ # brute force tree reorganization sub reorganize { warn "[" . __PACKAGE__ . "::reorganize] No longer implemented\n"; return undef; } #------------------------------------------------------------------------------ # returns the root of the XML document sub getRoot { my $self = shift; return $self->{'xpath'}->root; } #------------------------------------------------------------------------------ # returns the name of the document part (content, styles, meta, ...) sub getPartName { my $self = shift; my $name = $self->getRoot->getName; $name =~ s/^office:document-//; return $name; } #------------------------------------------------------------------------------ # returns the root element of the XML document sub getRootElement { my $self = shift; my $root = $self->{'xpath'}->root; my $rootname = $root->name() || ''; return ($rootname eq $self->{'element'}) ? $root : $root->first_child($self->{'element'}); } #------------------------------------------------------------------------------ # get/set/reset the current search context sub currentContext { my $self = shift; my $new_context = shift; $self->{'context'} = $new_context if (ref $new_context); return $self->{'context'}; } sub resetCurrentContext { my $self = shift; return $self->currentContext($self->getRoot); } #------------------------------------------------------------------------------ # returns the content class (text, spreadsheet, presentation, drawing) sub contentClass { my $self = shift; my $content_class = $self->getRootElement->getAttribute('office:class'); return $content_class if $content_class; my $body = $self->getBody or return undef; my $name = $body->name or return undef; $name =~ /(.*):(.*)/; return $2; } #------------------------------------------------------------------------------ # element name check sub getRootName { my $self = shift; return $self->getRootElement->name; } #------------------------------------------------------------------------------ # XML part type checks sub isMeta { my $self = shift; return ($self->getRootName() eq $XMLNAMES{'meta'}) ? 1 : undef; } sub isStyles { my $self = shift; return ($self->getRootName() eq $XMLNAMES{'styles'}) ? 1 : undef; } sub isSettings { my $self = shift; return ($self->getRootName() eq $XMLNAMES{'settings'}) ? 1 : undef; } #------------------------------------------------------------------------------ # returns the document body element (if defined) sub getBody { my $self = shift; return $self->{'body'} if ref $self->{'body'}; my $root = $self->getRoot; if ($self->{'body_path'}) { $self->{'body'} = $self->getElement ($self->{'body_path'}, 0, $root); return $self->{'body'}; } my $office_body = $self->getElement('//office:body', 0, $root); if ($office_body) { $self->{'body'} = $self->{'opendocument'} ? $office_body->selectChildElement ('office:(text|spreadsheet|presentation|drawing)') : $office_body; } else { $self->{'body'} = $self->getRootElement->selectChildElement ( 'office:(body|meta|master-styles|settings)' ); } return $self->{'body'}; } #------------------------------------------------------------------------------ # makes the current OODoc::XPath object share the same content as another one sub cloneContent { my $self = shift; my $source = shift; unless ($source && $source->{'xpath'}) { warn "[" . __PACKAGE__ . "::cloneContent] No valid source\n"; return undef; } $self->{'xpath'} = $source->{'xpath'}; $self->{'begin'} = $source->{'begin'}; $self->{'xml'} = $source->{'xml'}; $self->{'end'} = $source->{'end'}; return $self->getRoot; } #------------------------------------------------------------------------------ # exports an individual element as an XML string sub exportXMLElement { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); unless (defined $element) { warn "[" . __PACKAGE__ . "::exportXMLElement]] " . "Missing element\n"; return undef; } return $element->sprint(@_); } #------------------------------------------------------------------------------ # exports the document body (if defined) as an XML string sub exportXMLBody { my $self = shift; return $self->exportXMLElement($self->getBody, @_); } #------------------------------------------------------------------------------ # gets the reference of an XML element identified by path & position # for subsequent processing sub getElement { my $self = shift; my $path = shift; return undef unless $path; if (ref $path) { return $path->isElementNode ? $path : undef; } my $pos = shift || 0; my $context = shift || $self->{'context'} || $self->getRoot; if (defined $pos && (($pos =~ /^\d*$/) || ($pos =~ /^[\d+-]\d+$/))) { my $node = $self->selectNodeByXPath($context, $path, $pos); return $node && $node->isElementNode ? $node : undef; } else { warn "[" . __PACKAGE__ . "::getElement] " . "Invalid node position\n"; return undef; } } #------------------------------------------------------------------------------ # get the list of children (or the first child unless wantarray) matching # a given element name and belonging to a given element sub selectChildElementsByName { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->getElement($path, shift); return undef unless $element; return $element->selectChildElements(@_); } #------------------------------------------------------------------------------ # get the first child belonging to a given element and matching a given name sub selectChildElementByName { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->getElement($path, shift); return undef unless $element; return $element->selectChildElement(@_); } #----------------------------------------------------------------------------- # create a user field sub setUserFieldDeclaration { my $self = shift; my $name = shift or return undef; my %attr = ( type => 'string', value => "", @_ ); return undef if $self->getUserField($name); my $body = $self->getBody; my $context = $body->first_child('text:user-field-decls'); unless ($context) { $context = $self->appendElement ($body, 'text:user-field-decls'); } my $va = ( ($attr{'type'} eq 'float') || ($attr{'type'} eq 'currency') || ($attr{'type'} eq 'percentage') ) ? 'office:value' : "office:$attr{'type'}-value" ; $attr{'office:value-type'} = $attr{'type'}; $attr{$va} = $attr{'value'}; $attr{'text:name'} = $name; $attr{'office:currency'} = $attr{'currency'}; delete @attr{qw(type value currency)}; return $self->appendElement ( $context, 'text:user-field-decl', attributes => { %attr } ); } #----------------------------------------------------------------------------- # get user field element sub getUserField { my $self = shift; my $name = shift; unless ($name) { warn "[" . __PACKAGE__ . "::getUserField] Missing name\n"; return undef; } if (ref $name) { my $n = $name->getName; return ($n eq 'text:user-field-decl') ? $name : undef; } $name = $self->inputTextConversion($name); my $context = $self->getRoot(); if ($self->getPartName() eq 'styles') { $context = shift || $self->currentContext; } return $self->getNodeByXPath ( "//text:user-field-decl[\@text:name=\"$name\"]", $context ); } #----------------------------------------------------------------------------- # get user field list sub getUserFields { my $self = shift; my $context = $self->getRoot; if ($self->getPartName() eq 'styles') { $context = shift || $self->currentContext; } return $self->selectNodesByXPath('//text:user-field-decl', $context); } #----------------------------------------------------------------------------- # get/set user field value sub userFieldValue { my $self = shift; my $field = $self->getUserField(shift) or return undef; my $value = shift; my $value_att = $self->fieldValueAttributeName($field); if (defined $value) { if ($value) { $self->setAttribute($field, $value_att, $value); } else { $field->set_att($value_att => $value); } } return $self->getAttribute($field, $value_att); } #----------------------------------------------------------------------------- # get a variable element (contributed by Andrew Layton) sub getVariable { my $self = shift; my $name = shift; unless ($name) { warn "[" . __PACKAGE__ . "::getVariable] " . "Missing name\n"; return undef; } if (ref $name) { my $n = $name->getName; return ($n eq 'text:variable-set') ? $name : undef; } $name = $self->inputTextConversion($name); return $self->getNodeByXPath ("//text:variable-set[\@text:name=\"$name\"]"); } #----------------------------------------------------------------------------- # get/set the content of a variable element (contributed by Andrew Layton) sub variableValue { my $self = shift; my $variable = $self->getVariable(shift) or return undef; my $value = shift; my $value_att = $self->fieldValueAttributeName($variable); if (defined $value) { $self->setAttribute($variable, $value_att, $value); $self->setText($variable, $value); } $value = $self->getAttribute($variable, $value_att); return defined $value ? $value : $self->getText($variable); } #----------------------------------------------------------------------------- # some usual text field constructors sub create_field { my $self = shift; my $tag = shift; my %opt = @_; my $prefix = $opt{'-prefix'}; delete $opt{'-prefix'}; if ($prefix) { $tag = "$prefix:$tag" unless $tag =~ /:/; my %att = (); foreach my $k (keys %opt) { my $a = ($k =~ /:/) ? $k : "$prefix:$k"; $att{$a} = $opt{$k}; } %opt = %att; } my $element = OpenOffice::OODoc::Element->new($tag); $self->setAttributes($element, %opt); return $element; } sub spaces { my $self = shift; my $length = shift; return $self->create_field('text:s', 'text:c' => $length, @_); } sub tabStop { my $self = shift; my $tag = $self->{'opendocument'} ? 'text:tab' : 'text:tab-stop'; return $self->create_field($tag, @_); } sub lineBreak { my $self = shift; return $self->create_field('text:line-break', @_); } #------------------------------------------------------------------------------ sub appendLineBreak { my $self = shift; my $element = shift; return $element->appendChild('text:line-break'); } #------------------------------------------------------------------------------ sub appendSpaces { my $self = shift; my $element = shift; my $length = shift; my $spaces = $self->spaces($length) or return undef; $spaces->paste_last_child($element); } #------------------------------------------------------------------------------ # multiple whitespace handling routine, contributed by J David Eisenberg sub processSpaces { my $self = shift; my $element = shift; my $str = shift; my @words = split(/(\s\s+)/, $str); foreach my $word (@words) { if ($word =~ m/^ +$/) { $self->appendSpaces($element, length($word)); } elsif (length($word) > 0) { $element->appendTextChild($word); } } } #------------------------------------------------------------------------------ sub appendTabStop { my $self = shift; my $element = shift; my $tabtag = $self->{'opendocument'} ? 'text:tab' : 'text:tab-stop'; return $element->appendChild($tabtag); } #------------------------------------------------------------------------------ sub createFrameElement { my $self = shift; my %opt = @_; my %attr = (); $attr{'draw:name'} = $opt{'name'}; delete $opt{'name'}; my $content_class = $self->contentClass; $attr{'draw:style-name'} = $opt{'style'}; delete $opt{'style'}; if ($opt{'page'}) { my $pg = $opt{'page'}; if (ref $pg) { $opt{'attachment'} = $pg unless $opt{'attachment'}; } elsif ($content_class eq 'text') { $opt{'attachment'} = $self->{'body'}; $attr{'text:anchor-type'} = 'page'; $attr{'text:anchor-page-number'} = $pg; } elsif ( ($content_class eq 'presentation') or ($content_class eq 'drawing') ) { my $n = $self->inputTextConversion($pg); $opt{'attachment'} = $self->getNodeByXPath ("//draw:page[\@draw:name=\"$n\"]"); } } delete $opt{'page'}; my $tag = $opt{'tag'} || 'draw:frame'; delete $opt{'tag'}; my $frame = OpenOffice::OODoc::XPath::new_element($tag); if ($opt{'position'}) { $self->setObjectCoordinates($frame, $opt{'position'}); delete $opt{'position'}; } if ($opt{'size'}) { $self->setObjectSize($frame, $opt{'size'}); delete $opt{'size'}; } if ($opt{'description'}) { $self->setObjectDescription($frame, $opt{'description'}); delete $opt{'description'}; } if ($opt{'attachment'}) { $frame->paste_first_child($opt{'attachment'}); delete $opt{'attachment'}; } foreach my $k (keys %opt) { $attr{$k} = $opt{$k} if ($k =~ /:/); } $self->setAttributes($frame, %attr); return $frame; } sub createFrame { my $self = shift; return $self->createFrameElement(@_); } #----------------------------------------------------------------------------- # select an individual frame element by name sub selectFrameElementByName { my $self = shift; my $text = $self->inputTextConversion(shift); my $tag = shift || 'draw:frame'; return $self->selectNodeByXPath ("//$tag\[\@draw:name=\"$text\"\]", @_); } #----------------------------------------------------------------------------- # gets frame element (name or ref, with type checking) sub getFrameElement { my $self = shift; my $frame = shift; return undef unless defined $frame; my $tag = shift || 'draw:frame'; my $element = undef; if (ref $frame) { $element = $frame; } else { if ($frame =~ /^[\-0-9]*$/) { return $self->getElement("//$tag", $frame, @_); } else { return $self->selectFrameElementByName ($frame, $tag, @_); } } } #------------------------------------------------------------------------------ sub getFrameList { my $self = shift; return $self->getDescendants('draw:frame', shift); } #------------------------------------------------------------------------------ sub frameStyle { my $self = shift; my $frame = $self->getFrameElement(shift) or return undef; my $style = shift; my $attr = 'draw:style-name'; return (defined $style) ? $self->setAttribute($frame, $attr => shift) : $self->getAttribute($frame, $attr); } #------------------------------------------------------------------------------ # replaces any previous content of an existing element by a given text # without processing other than encoding sub setFlatText { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->OpenOffice::OODoc::XPath::getElement ($path, shift); return undef unless $element; my $text = shift; my $t = $self->inputTextConversion($text); return undef unless defined $t; $element->set_text($t); return $text; } #------------------------------------------------------------------------------ # replaces any previous content of an existing element by a given text # processing tab stops and line breaks sub setText { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->OpenOffice::OODoc::XPath::getElement ($path, shift); return undef unless $element; my $text = shift; return undef unless defined $text; unless ($text) { $element->set_text($text); return $text; } return $self->setFlatText($element, $text) if $element->isTextNode; my $tabtag = $self->{'opendocument'} ? 'text:tab' : 'text:tab-stop'; $element->set_text(""); my @lines = split "\n", $text; while (@lines) { my $line = shift @lines; my @columns = split "\t", $line; while (@columns) { my $column = $self->inputTextConversion(shift @columns); unless ($self->{'multiple_spaces'}) { $element->appendTextChild($column); } else { $self->processSpaces($element, $column); } $element->appendChild($tabtag) if (@columns); } $element->appendChild('text:line-break') if (@lines); } $element->normalize; return $text; } #------------------------------------------------------------------------------ # extends the text of an existing element sub extendText { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $text = shift; return undef unless defined $text; my $element = $self->getElement($path, $pos); return undef unless $element; my $offset = shift; if (ref $text) { if ($text->isElementNode) { unless (defined $offset) { $text->paste_last_child($element); } else { $text->paste_within($element, $offset); } } return $text; } my $tabtag = $self->{'opendocument'} ? 'text:tab' : 'text:tab-stop'; my @lines = split "\n", $text; my $ref_node = undef; while (@lines) { my $line = shift @lines; my @columns = split "\t", $line; while (@columns) { my $column = $self->inputTextConversion(shift @columns); unless ($ref_node) { $ref_node = $element->insertTextChild ($column, $offset); $ref_node = $ref_node->insertNewNode ($tabtag, 'after') if (@columns); } else { my $tn = $self->createTextNode($column); $ref_node = $ref_node->insertNewNode ($tn, 'after'); $ref_node = $ref_node->insertNewNode ($tabtag, 'after') if (@columns); } } if (@lines) { if ($ref_node) { $ref_node->insertNewNode ('text:line-break', 'after'); } else { $element->insertNewNode ( 'text:line-break', 'within', $offset ); } } } $element->normalize; return $text; } #------------------------------------------------------------------------------ # converts the content of an element to flat text sub flatten { my $self = shift; my $element = shift || $self->{'context'}; return $element->flatten; } #------------------------------------------------------------------------------ # creates a new encoded text node sub newTextNode { my $self = shift; my $text = $self->inputTextConversion(shift) or return undef; return OpenOffice::OODoc::Element->new('#PCDATA' => $text); } #------------------------------------------------------------------------------ # gets decoded text without other processing sub getFlatText { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->OpenOffice::OODoc::XPath::getElement ($path, @_); return undef unless $element; return $self->outputTextConversion($element->text); } #------------------------------------------------------------------------------ # gets text in element by path (sub-element texts are concatenated) sub getText { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->OpenOffice::OODoc::XPath::getElement ($path, @_); return undef unless $element; return $self->getFlatText($element) if $element->isTextNode; return undef unless $element->isElementNode; my $text = ''; my $name = $element->getName; if ($name =~ /^text:tab(|-stop)$/) { return "\t"; } if ($name eq 'text:line-break') { return "\n"; } if ($name eq 'text:s') { my $spaces = ""; my $count = $element->att('text:c') || 1; while ($count > 0) { $spaces .= ' '; $count--; } return $spaces; } foreach my $node ($element->getChildNodes) { if ($node->isElementNode) { $text .= $self->getText($node); } else { $text .= $self->outputTextConversion($node->text); } } return $text; } #------------------------------------------------------------------------------ sub xpathInContext { my $self = shift; my $path = shift || "/"; my $context = shift || $self->{'context'}; if ($context ne $self->{'xpath'}) { $path =~ s/^\//\.\//; } return ($path, $context); } #------------------------------------------------------------------------------ sub getDescendants { my $self = shift; my $tag = shift; my $context = shift || $self->{'context'}; return $context->descendants($tag, @_); } #------------------------------------------------------------------------------ sub getTextNodes { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->getElement($path, shift) or return undef; my $filter = $self->inputTextConversion(shift); return $element->getTextDescendants($filter); } #------------------------------------------------------------------------------ # brute XPath nodelist selection; allows any XML::XPath expression sub selectNodesByXPath { my $self = shift; my ($p1, $p2) = @_; my $path = undef; my $context = undef; if (ref $p1) { $context = $p1; $path = $p2; } else { $path = $p1; $context = $p2; } ($path, $context) = $self->xpathInContext($path, $context); unless (ref $context) { warn "[" . __PACKAGE__ . "::selectNodesByXPath] " . "Bad context argument\n"; return undef; } return $context->get_xpath($path); } #------------------------------------------------------------------------------ # like selectNodesByXPath, without variable context (direct XML::Twig method) sub get_xpath { my $self = shift; return $self->{'xpath'}->get_xpath(@_); } #------------------------------------------------------------------------------ # brute XPath single node selection; allows any XML::XPath expression sub selectNodeByXPath { my $self = shift; my $p1 = shift; my $p2 = shift; my $offset = shift || 0; my $path = undef; my $context = undef; if (ref $p1) { $context = $p1; $path = $p2; } else { $path = $p1; $context = $p2; } ($path, $context) = $self->xpathInContext($path, $context); unless (ref $context) { warn "[" . __PACKAGE__ . "::selectNodeByXPath] " . "Bad context argument\n"; return undef; } return $context->get_xpath($path, $offset); } #------------------------------------------------------------------------------ # brute XPath value extraction; allows any XML::XPath expression sub getXPathValue { my $self = shift; my ($p1, $p2) = @_; my $path = undef; my $context = undef; if (ref $p1) { $context = $p1; $path = $p2; } else { $path = $p1; $context = $p2; } ($path, $context) = $self->xpathInContext($path, $context); unless (ref $context) { warn "[" . __PACKAGE__ . "::getXPathValue] " . "Bad context argument\n"; return undef; } return $self->outputTextConversion($context->findvalue($path, @_)); } #------------------------------------------------------------------------------ # create or update an xpath sub makeXPath { my $self = shift; my $path = shift; my $root = undef; if (ref $path) { $root = $path; $path = shift; } else { $root = $self->getRoot; } $path =~ s/^[\/ ]*//; $path =~ s/[\/ ]*$//; my @list = split '/', $path; my $posnode = $root; while (@list) { my $item = shift @list; while (($item =~ /\[.*/) && !($item =~ /\[.*\]/)) { my $cont = shift @list or last; $item .= ('/' . $cont); } next unless $item; my $node = undef; my $name = undef; my $param = undef; $item =~ s/\[(.*)\] *//; $param = $1; $name = $item; $name =~ s/^ *//; $name =~ s/ *$//; my %attributes = (); my $text = undef; my $indice = undef; if ($param) { my @attrlist = []; $indice = undef; $param =~ s/^ *//; $param =~ s/ *$//; $param =~ s/^@//; @attrlist = split /@/, $param; foreach my $a (@attrlist) { next unless $a; $a =~ s/^ *//; my $tmp = $a; $tmp =~ s/ *$//; if ($tmp =~ /^\d*$/) { $indice = $tmp; next; } if ($a =~ s/^\"(.*)\".*/$1/) { $text = $1; next; } if ($a =~ /^=/) { $a =~ s/^=//; $a =~ '^"(.*)"$'; $text = $1 ? $1 : $a; next; } $a =~ s/^@//; my ($attname, $attvalue) = split '=', $a; next unless $attname; if ($attvalue) { $attvalue =~ '"(.*)"'; $attvalue = $1 if $1; } $attname =~ s/^ *//; $attname =~ s/ *$//; $attributes{$attname} = $attvalue; } } if (defined $indice) { $node = $self->getNodeByXPath ($posnode, "$name\[$indice\]"); } else { $node = $self->getChildElementByName($posnode, $name); } if ($node) { $self->setAttributes($node, %attributes); $self->setText($node, $text) if (defined $text); } else { $node = $self->appendElement ( $posnode, $name, text => $text, attributes => {%attributes} ); } if ($node) { $posnode = $node; } else { return undef; } } return $posnode; } #------------------------------------------------------------------------------ # selects element by path and attribute sub selectElementByAttribute { my $self = shift; my $path = shift or return undef; my $key = shift or return undef; my $arg3 = shift; my $xp = undef; if (defined $arg3 && ! ref $arg3) # arg3 = value { my $value = $self->inputTextConversion($arg3); $xp = "//$path\[\@$key=\"$value\"\]"; } else # arg3 = undef or context { $xp = "//$path\[\@$key\]" ; unshift @_, $arg3; } return $self->selectNodeByXPath($xp, @_); } #------------------------------------------------------------------------------ sub selectElementByIdentifier { my $self = shift; return $self->selectElementByAttribute(shift, $ELT_ID, @_); } #------------------------------------------------------------------------------ # selects list of elements by path and attribute sub selectElementsByAttribute { my $self = shift; my $path = shift or return undef; my $key = shift or return undef; my $arg3 = shift; my $xp = undef; if (defined $arg3 && ! ref $arg3) # arg3 = value { my $value = $self->inputTextConversion($arg3); $xp = "//$path\[\@$key=\"$value\"\]"; } else # arg3 = undef or context { $xp = "//$path\[\@$key\]" ; unshift @_, $arg3; } return wantarray ? $self->selectNodesByXPath($xp, @_) : $self->selectNodeByXPath($xp, @_); } #------------------------------------------------------------------------------ # get a list of elements matching a given path and an optional content pattern sub findElementList { my $self = shift; my $path = shift; my $pattern = shift; my $replace = shift; my $context = shift; return undef unless $path; my @result = (); ($path, $context) = $self->xpathInContext($path, $context); foreach my $n ($context->findnodes($path)) { push @result, [ $self->findDescendants($n, $pattern, $replace, @_) ]; } return @result; } #------------------------------------------------------------------------------ # get a list of elements matching a given path and an optional content pattern # without replacement operation, and from an optional context node sub selectElements { my $self = shift; my $path = shift; my $context = $self->{'context'}; if (ref $path) { $context = $path; $path = shift; } my $filter = shift; my @candidates = $self->selectNodesByXPath($context, $path); return @candidates unless $filter; my @result = (); while (@candidates) { my $node = shift @candidates; push @result, $node if $self->_search_content($node, $filter, @_, $node); } return @result; } #------------------------------------------------------------------------------ # get the 1st element matching a given path and on optional content pattern sub selectElement { my $self = shift; my $path = shift; my $context = $self->{'context'}; if (ref $path) { $context = $path; $path = shift; } return undef unless $path; my $filter = shift; my @candidates = $self->selectNodesByXPath($context, $path); return $candidates[0] unless $filter; while (@candidates) { my $node = shift @candidates; return $node if $self->_search_content($node, $filter, @_, $node); } return undef; } #------------------------------------------------------------------------------ # gets the descendants of a given node, with optional in fly search/replacement sub findDescendants { my $self = shift; my $node = shift; my $pattern = shift; my $replace = shift; my @result = (); my $n = $self->selectNodeByContent($node, $pattern, $replace, @_); push @result, $n if $n; foreach my $m ($node->getChildNodes) { push @result, [ $self->findDescendants($m, $pattern, $replace, @_) ]; } return @result; } #------------------------------------------------------------------------------ # search & replace text in an individual node sub selectNodeByContent { my $self = shift; my $node = shift; my $pattern = shift; my $replace = shift; return $node unless $pattern; my $l = $node->text; return undef unless $l; unless (defined $replace) { return ($l =~ /$pattern/) ? $node : undef; } else { if (ref $replace) { unless ($l =~ s/($pattern)/&$replace(@_, $node, $1)/eg) { return undef; } } else { unless ($l =~ s/$pattern/$replace/g) { return undef; } } $node->set_text($l); return $node; } } #------------------------------------------------------------------------------ # gets the text content of a nodelist sub getTextList { my $self = shift; my $path = shift; my $pattern = shift; my $context = shift; return undef unless $path; ($path, $context) = $self->xpathInContext($path, $context); my @nodelist = $context->findnodes($path); my @text = (); foreach my $n (@nodelist) { my $l = $self->outputTextConversion($n->string_value); push @text, $l if ((! defined $pattern) || ($l =~ /$pattern/)); } return wantarray ? @text : join "\n", @text; } #------------------------------------------------------------------------------ # gets the attributes of an element in the key => value form sub getAttributes { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $node = $self->getElement($path, $pos, @_); return undef unless $path; my %attributes = (); my $aa = $node->atts(@_); my %atts = %{$aa} if $aa; foreach my $a (keys %atts) { $attributes{$a} = $self->outputTextConversion($atts{$a}); } return %attributes; } #------------------------------------------------------------------------------ # gets the value of an attribute by path + name sub getAttribute { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $name = shift or return undef; my $node = $self->getElement($path, $pos, @_); unless ($name =~ /:/) { my $prefix = $node->ns_prefix; $name = $prefix . ':' . $name if $prefix; } $name =~ s/ /-/g; return $self->outputTextConversion($node->att($name)); } #------------------------------------------------------------------------------ # set/replace a list of attributes in an element sub setAttributes { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my %attr = @_; my $node = $self->getElement($path, $pos, $attr{'context'}); return undef unless $node; my $prefix = $node->ns_prefix(); foreach my $k (keys %attr) { my $att_name = $k; $att_name =~ s/ /-/g; if (!($k =~ /:/) && $prefix) { $att_name = $prefix . ':' . $att_name; } if (defined $attr{$k}) { $node->set_att ( $att_name, $self->inputTextConversion($attr{$k}) ); } else { $node->del_att($att_name) if $node->att($att_name); } } return %attr; } #------------------------------------------------------------------------------ # set/replace a single attribute in an element sub setAttribute { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $attribute = shift or return undef; my $value = shift; my $node = $self->getElement($path, $pos, @_) or return undef; $attribute =~ s/ /-/g; unless ($attribute =~ /:/) { my $prefix = $node->ns_prefix; $attribute = $prefix . ':' . $attribute if $prefix; } if (defined $value) { $node->set_att ( $attribute, $self->inputTextConversion($value) ); } else { $node->del_att($attribute) if $node->att($attribute); } return $value; } #------------------------------------------------------------------------------ # removes an attribute in element sub removeAttribute { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $name = shift or return undef; my $node = $self->getElement($path, $pos, @_) or return undef; unless ($name =~ /:/) { my $prefix = $node->ns_prefix; $name = $prefix . ':' . $name if $prefix; } return $node->del_att($name) if $node->att($name); } #------------------------------------------------------------------------------ # replicates an existing element, provided as an XPath ref or an XML string sub replicateElement { my $self = shift; my $proto = shift; my $position = shift; my %options = @_; unless ($proto && ref $proto && $proto->isElementNode) { warn "[" . __PACKAGE__ . "::replicateElement] No prototype\n"; return undef; } $position = 'end' unless $position; my $element = $proto->copy; $self->setAttributes($element, %{$options{'attribute'}}); if (ref $position) { if (! $options{'position'}) { $element->paste_last_child($position); } elsif ($options{'position'} eq 'before') { $element->paste_before($position); } elsif ($options{'position'} eq 'after') { $element->paste_after($position); } elsif ($options{'position'} ne 'free') { warn "[" . __PACKAGE__ . "::replicateElement] " . "No valid attachment option\n"; } } elsif ($position eq 'end') { $element->paste_last_child($self->{'xpath'}->root); } elsif ($position eq 'body') { $element->paste_last_child($self->getBody); } return $element; } #------------------------------------------------------------------------------ # create an element, just with a mandatory name and an optional text # the name can have the namespace:name form # if the $name argument is a '<.*>' string, it's processed as XML and # the new element is completely generated from it sub createElement { my $self = shift; my $name = shift; my $text = shift; my $element = OpenOffice::OODoc::XPath::new_element($name, @_); unless ($element) { warn "[" . __PACKAGE__ . "::createElement] " . "Element creation failure\n"; return undef; } $self->setText($element, $text) if defined $text; return $element; } #------------------------------------------------------------------------------ # replaces an element by another one # the new element is inserted before the old one, # then the old element is removed. # the new element can be inserted by copy (default) or by reference # return = new element if success, undef if failure sub replaceElement { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $new_element = shift; my %options = ( mode => 'copy', @_ ); unless ($new_element) { warn "[" . __PACKAGE__ . "::replaceElement] " . "Missing new element\n"; return undef; } unless (ref $new_element) { $new_element = $self->createElement($new_element); $options{'mode'} = 'reference'; } unless ($new_element && $new_element->isElementNode) { warn "[" . __PACKAGE__ . "::replaceElement] " . "No valid replacement\n"; return undef; } my $result = undef; my $old_element = $self->getElement ($path, $pos, $options{'context'}); unless ($old_element) { warn "[" . __PACKAGE__ . "::replaceElement] " . "Non existing element to be replaced\n"; return undef; } if (! $options{'mode'} || $options{'mode'} eq 'copy') { $result = $new_element->copy; $result->replace($old_element); return $result; } elsif ($options{'mode'} && $options{'mode'} eq 'reference') { $result = $self->insertElement ( $old_element, $new_element, position => 'before' ); $old_element->delete; return $result; } else { warn "[" . __PACKAGE__ . "::replaceElement] " . "Unknown option\n"; } return undef; } #------------------------------------------------------------------------------ # appends a new or existing child element to any existing element sub appendElement { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $name = shift; my %opt = @_; $opt{'attribute'} = $opt{'attributes'} unless ($opt{'attribute'}); return undef unless $name; my $element = undef; unless (ref $name) { $element = $self->createElement($name, $opt{'text'}); } else { $element = $name; $self->setText($element, $opt{'text'}) if $opt{'text'}; } return undef unless $element; my $parent = $self->getElement ($path, $pos, $opt{'context'}); unless ($parent) { warn "[" . __PACKAGE__ . "::appendElement] Position not found\n"; return undef; } $element->paste_last_child($parent); $self->setAttributes($element, %{$opt{'attribute'}}); return $element; } #----------------------------------------------------------------------------- # append an element to the document body sub appendBodyElement { my $self = shift; return $self->appendElement($self->{'body'}, @_); } #------------------------------------------------------------------------------ # appends a list of children to an existing element sub appendElements { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $parent = $self->getElement($path, $pos) or return undef; my @children = @_; foreach my $child (@children) { $parent->appendChild($child); } return $parent; } #------------------------------------------------------------------------------ # cuts a set of existing elements and pastes them as children of a given one sub moveElements { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $parent = $self->getElement($path, $pos) or return undef; $parent->pickUpChildren(@_); return $parent; } #------------------------------------------------------------------------------ # selects a text node in a given element according to offset & expression sub textIndex { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift) or return undef; my %opt = @_; my $offset = $opt{'offset'}; my $way = $opt{'way'} || 'forward'; if (defined $offset && $offset < 0) { $way = 'backward'; } $offset = -abs($offset) if defined $offset && $way eq 'backward'; my $start_mark = $opt{'start_mark'}; my $end_mark = $opt{'end_mark'}; my $expr = undef; if (defined $opt{'after'}) { $expr = $opt{'after'}; delete @opt{qw(before replace capture content)}; } elsif (defined $opt{'before'}) { $expr = $opt{'before'}; delete @opt{qw(replace capture content)}; } else { $expr = $opt{'content'} || $opt{'replace'} || $opt{'capture'}; } $expr = $self->inputTextConversion($expr); my $node = undef; my $node_text = undef; my $node_length = undef; my $found = undef; my $end_pos = undef; my $match = undef; if ($way ne 'backward') # positive offset, forward { if ($element->isTextNode) { $node = $element; } elsif ($start_mark) { unless($start_mark->isTextNode) { my $n = $start_mark->last_descendant; $start_mark = $n if $n; $node = $n->next_elt($element, '#PCDATA'); } else { $node = $start_mark; } } else { $node = $element->first_descendant('#PCDATA'); } if ($end_mark && ! $node->before($end_mark)) { $node = undef; } ($node_length, $node_text) = $node->textLength if $node; FORWARD_LOOP: while ($node && !defined $found) { if ($end_mark && ! $node->before($end_mark)) { $node = undef; last; } if (defined $offset && ($offset > $node_length)) { # skip node $offset -= $node_length; $node = $node->next_elt($element, '#PCDATA'); ($node_length, $node_text) = $node->textLength if $node; } elsif (defined $expr) { # look for substring my $text = $node->text() || ""; if (defined $offset && $offset > 0) { $text = substr($text, $offset); } if ($text =~ /($expr)/) { $found = length($`); $found += $offset if defined $offset; $end_pos = $found + length($&); $match = $1; } unless (defined $found) { $offset = undef; $node = $node->next_elt ($element, '#PCDATA'); } } else # selected by offset { $found = $offset || 0; } } } else # negative offset, backward { if ($element->isTextNode) { $node = $element; } elsif ($start_mark) { unless ($start_mark->isTextNode) { $node = $start_mark->prev_elt('#PCDATA'); } else { $node = $start_mark; } } else { $node = $element->last_descendant('#PCDATA'); } if ($end_mark) { my $n = $end_mark->last_descendant; $end_mark = $n if $n; $node = undef if ($end_mark && ! $node->after($end_mark)); } ($node_length, $node_text) = $node->textLength if $node; BACKWARD_LOOP: while ($node && !defined $found) { if ($end_mark && ! $node->after($end_mark)) { $node = undef; last; } ($node_length, $node_text) = $node->textLength; if (defined $offset && (abs($offset) > $node_length)) { # skip node $offset += $node_length; $node = $node->prev_elt($element, '#PCDATA'); } elsif (defined $expr) { my $text = $node->text() || ""; if (defined $offset && $offset < 0) { $text = substr($text, 0, $offset); } my @r = ($text =~ m/($expr)/g); if (@r) { $found = length($`); $end_pos = $found + length($&); $match = $1; } unless (defined $found) { $offset = undef; $node = $node->prev_elt ($element, '#PCDATA'); } } else # selected by offset { $found = $offset || 0; } } } return ($node, $found, $end_pos, $match); } #------------------------------------------------------------------------------ # creates new child elements in a given element and splits the content # according to a regexp sub splitContent { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $context = $self->getElement($path, $pos) or return undef; my $tag = shift or return undef; my $expr = $self->inputTextConversion(shift); return undef unless defined $expr; my %opt = @_; my $prefix = undef; if ($tag =~ /(.*):/) { $prefix = $1 || 'text'; } else { $prefix = $context->ns_prefix() || 'text'; $tag = $prefix . ':' . $tag; } my %attr = (); foreach my $k (keys %opt) { my $a = $self->inputTextConversion($opt{$k}); $k = $prefix . ':' . $k unless $k =~ /:/; $attr{$k} = $a; } %opt = (); return $context->mark("($expr)", $tag, { %attr }); } #------------------------------------------------------------------------------ # creates a child element in place within an existing element # at a given position or before/after a given substring sub setChildElement { my $self = shift; my $path = shift; my $node = (ref $path) ? $path : $self->getElement($path, shift) or return undef; my $name = shift or return undef; my %opt = @_; if (defined $opt{'text'}) { $opt{'replace'} = $opt{'capture'} unless defined $opt{'replace'}; delete $opt{'capture'}; } my $newnode = undef; my $function = undef; if (ref $name) { if ((ref $name) eq 'CODE') { $function = $name; $name = undef; } else { $newnode = $name; } } else { unless ($name =~ /:/ || $name =~ /^#/) { my $prefix = $node->ns_prefix() || 'text'; $name = $prefix . ':' . $name; } $newnode = OpenOffice::OODoc::XPath::new_element($name); } my $offset = $opt{'offset'} || 0; if (lc($offset) eq 'end') { unless ($function) { $newnode->paste_last_child($node); } else { $newnode = &$function($self, $node, 'end'); } } elsif (lc($offset) eq 'start') { unless ($function) { $newnode->paste_first_child($node); } else { $newnode = &$function($self, $node, 'start'); } } else { my ($text_node, $start_pos, $end_pos, $match) = $self->textIndex($node, %opt); if ($text_node) { if (defined $opt{'replace'} || defined $opt{'capture'}) { my $t = $text_node->text; substr ( $t, $start_pos, $end_pos - $start_pos, "" ); $text_node->set_text($t); unless ($function) { $newnode->paste_within ($text_node, $start_pos); $newnode->set_text($match) if defined $opt{'capture'}; } else { $newnode = &$function ( $self, $text_node, $start_pos, $match ); } } else { my $p = defined $opt{'after'} ? $end_pos : $start_pos; unless ($function) { $newnode->paste_within($text_node, $p); } else { $newnode = &$function ( $self, $text_node, $p, $match ); } } } else { return undef; } } if ($newnode) { $self->setAttributes($newnode, %{$opt{'attributes'}}); $self->setText($newnode, $opt{'text'}) unless is_true($opt{'no_text'}); } return $newnode; } #------------------------------------------------------------------------------ # create successive child elements sub setChildElements { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $element = $self->getElement($path, $pos) or return undef; my $name = shift or return undef; my %opt = @_; my @elements = (); my $node = $self->setChildElement($element, $name, %opt); push @elements, $node if $node; if (defined $opt{'text'}) { $opt{'replace'} = $opt{'capture'} unless defined $opt{'replace'}; delete $opt{'capture'}; } delete $opt{'attributes'}; delete $opt{'text'}; delete $opt{'offset'} if ( defined $opt{'after'} || defined $opt{'before'} || defined $opt{'replace'} || defined $opt{'capture'} ); $opt{'offset'} = 1 if ( ($opt{'way'} ne 'backward' && defined $opt{'before'}) || ($opt{'way'} eq 'backward' && defined $opt{'after'}) ); while ($node) { my $arg = ref($name) eq 'CODE' ? $name : $node->copy; $node = $self->setChildElement ($element, $arg, %opt, start_mark => $node); push @elements, $node if $node; } return @elements; } #------------------------------------------------------------------------------ sub markElement { my $self = shift; my $context = shift or return undef; my $tag = shift; my $expression = $self->inputTextConversion(shift); my %attr = @_; return $context->mark("($expression)", $tag, { %attr }); } #------------------------------------------------------------------------------ # inserts a new element before or after a given node sub insertElement { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $name = shift; my %opt = @_; $opt{'attributes'} = $opt{'attribute'} unless $opt{'attributes'}; return undef unless $name; my $element = undef; unless (ref $name) { $element = $self->createElement($name, $opt{'text'}); } else { $element = $name; $self->setText($element, $opt{'text'}) if $opt{'text'}; } return undef unless $element; my $posnode = $self->getElement($path, $pos, $opt{'context'}); unless ($posnode) { warn "[" . __PACKAGE__ . "::insertElement] Unknown position\n"; return undef; } if ($opt{'position'}) { if ($opt{'position'} eq 'after') { $element->paste_after($posnode); } elsif ($opt{'position'} eq 'before') { $element->paste_before($posnode); } elsif ($opt{'position'} eq 'within') { my $offset = $opt{'offset'} || 0; $element->paste_within($posnode, $offset); } else { warn "[" . __PACKAGE__ . "::insertElement] " . "Invalid $opt{'position'} option\n"; return undef; } } else { $element->paste_before($posnode); } $self->setAttributes($element, %{$opt{'attributes'}}); return $element; } #------------------------------------------------------------------------------ # removes the given element & children sub removeElement { my $self = shift; my $e = $self->getElement(@_); return undef unless $e; return $e->delete; } #------------------------------------------------------------------------------ # cuts the given element & children (to be pasted elsewhere) sub cutElement { my $self = shift; my $e = $self->getElement(@_); return undef unless $e; $e->cut; return $e; } #----------------------------------------------------------------------------- # splits a text element at a given offset sub splitElement { my $self = shift; my $path = shift; my $old_element = (ref $path) ? $path : $self->getElement($path, shift); my $offset = shift; my $new_element = $old_element->split_at($offset); $new_element->set_atts($old_element->atts); return wantarray ? ($old_element, $new_element) : $new_element; } #------------------------------------------------------------------------------ # get/set ODF element identifier sub getIdentifier { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); return $self->outputTextConversion($element->getID()); } sub setIdentifier { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); my $value = shift; return (defined $value) ? $self->inputTextConversion($element->setID($value)) : $self->removeIdentifier($element); } sub identifier { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); my $value = shift; return (defined $value) ? $self->setIdentifier($element, $value) : $self->getIdentifier($element); } sub removeIdentifier { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); return $element->setID(); } sub getElementName { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); my $attr = $element->ns_prefix() . ':name'; return $self->getAttribute($element, $attr); } sub setElementName { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); my $attr = $element->ns_prefix() . ':name'; return $self->setAttribute($element, $attr => shift); } sub elementName { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); my $value = shift; return (defined $value) ? $self->setElementName($element, $value) : $self->getElementName($element); } #------------------------------------------------------------------------------ # some extensions for XML Twig elements package OpenOffice::OODoc::Element; our @ISA = qw ( XML::Twig::Elt ); #------------------------------------------------------------------------------ BEGIN { *identifier = *ID; *getPrefix = *XML::Twig::Elt::ns_prefix; *getNodeValue = *XML::Twig::Elt::text; *getValue = *XML::Twig::Elt::text; *setNodeValue = *XML::Twig::Elt::set_text; *getAttribute = *XML::Twig::Elt::att; *setName = *XML::Twig::Elt::set_tag; *getParentNode = *XML::Twig::Elt::parent; *getDescendantTextNodes = *getTextDescendants; *dispose = *XML::Twig::Elt::delete; } sub hasTag { my $node = shift; my $name = $node->getName; my $value = shift; return ($name && ($name eq $value)) ? 1 : undef; } sub isFrame { my $node = shift; return $node->hasTag('draw:frame'); } sub getLocalPosition { my $node = shift; my $tag = (shift || $node->getName) or return undef; my $xpos = $node->pos($tag); return defined $xpos ? $xpos - 1 : undef; } sub selectChildElements { my $node = shift; my $filter = shift; my $condition = ref $filter ? $filter : qr($filter); return $node->children($condition); } sub selectChildElement { my $node = shift; my $filter = shift; my $pos = shift || 0; my $count = 0; my $fc = $node->first_child; return $fc unless defined $filter; my $name = $fc->name if $fc; while ($fc) { if ($name && ($name =~ /$filter/)) { return $fc if ($count >= $pos); $count++; } $fc = $fc->next_sibling; $name = $fc->name if $fc; } return undef; } sub getFirstChild { my $node = shift; my $fc = $node->first_child(@_); my $name = $fc->name if $fc; while ($name && ($name =~ /^#/)) { $fc = $fc->next_sibling(@_); $name = $fc->name if $fc; } return $fc; } sub getLastChild { my $node = shift; my $lc = $node->last_child(@_); my $name = $lc->name; while ($name && ($name =~ /^#/)) { $lc = $lc->prev_sibling(@_); $name = $lc->name; } return $lc; } sub getChildrenTextNodes { my $node = shift; return $node->children('#PCDATA'); } sub getChildTextNode { my $node = shift; my $pos = shift || 0; my @children = $node->children('#PCDATA'); return $children[$pos]; } sub getTextDescendants { my ($node, $filter) = @_; return defined $filter ? $node->get_xpath('#PCDATA[string()=~/' . $filter . '/]') : $node->descendants('#PCDATA'); } sub textLength # length of a text node { my $node = shift; my $text = $node->text; my $length = length($text); return wantarray ? ($length, $text) : $length; } sub appendChild { my $node = shift; my $child = shift; unless (ref $child) { $child = OpenOffice::OODoc::XPath::new_element($child, @_); } return $child->paste_last_child($node); } sub pickUpChildren { my $parent = shift; my @children = @_; foreach my $child (@children) { $child->move(last_child => $parent); } return $parent; } sub insertNewNode { my $node = shift; my $newnode = shift or return undef; my $position = shift; # 'before', 'after', 'within', ... my $offset = shift; unless (ref $newnode) { $newnode = OpenOffice::OODoc::XPath::new_element($newnode, @_); } if (defined $offset) { return $newnode->paste($position => $node, $offset); } else { return $newnode->paste($position => $node); } } sub insertNodes { my $node = shift; my $offset = shift; my $child = shift or return undef; $child->paste_within($node, $offset); my $count = 1; while (@_) { my $next_child = shift; $next_child->paste_after($child); $child = $next_child; $count++; } return $count; } sub replicateNode { my $node = shift; my $number = shift; $number = 1 unless defined $number; my $position = shift || 'after'; my $last_node = $node; while ($number > 0) { my $newnode = $node->copy; $newnode->paste($position => $last_node); $last_node = $newnode; $number--; } return $last_node; } sub flatten { my $node = shift; return $node->set_text($node->text); } sub appendTextChild { my $node = shift; my $text = shift; return undef unless defined $text; my $text_node = OpenOffice::OODoc::Element->new('#PCDATA' => $text); return $text_node->paste_last_child($node); } sub insertTextChild { my $node = shift; my $text = shift; return undef unless defined $text; my $offset = shift; return $node->appendTextChild($text) unless defined $offset; my $text_node = OpenOffice::OODoc::Element->new('#PCDATA' => $text); return $offset > 0 ? $text_node->paste_within($node, $offset) : $text_node->paste_first_child($node); } sub getAttributes { my $node = shift; return %{$node->atts(@_) || {}}; } sub setAttribute { my $node = shift or return undef; my $attribute = shift; my $value = shift; if (defined $value) { return $node->set_att($attribute, $value, @_); } else { return $node->removeAttribute($attribute); } } sub setID { my $node = shift; return $node->setAttribute($ELT_ID, shift); } sub getID { my $node = shift; return $node->getAttribute($ELT_ID); } sub ID { my $node = shift; my $new_id = shift; return (defined $new_id) ? $node->setID($new_id) : $node->getID(); } sub removeAttribute { my $node = shift or return undef; my $attribute = shift or return undef; return $node->att($attribute) ? $node->del_att($attribute) : undef; } #------------------------------------------------------------------------------ 1; OpenOffice-OODoc-2.125/OODoc/Styles.pm0000644000175000017500000011533111416636072015413 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : Styles.pm 2.027 2010-07-12 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2010 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- package OpenOffice::OODoc::Styles; use 5.008_000; use strict; our $VERSION = '2.027'; use OpenOffice::OODoc::XPath 2.237; use File::Basename; require Exporter; our @ISA = qw ( Exporter OpenOffice::OODoc::XPath ); our @EXPORT = qw ( odfLoadColorMap ooLoadColorMap oo2rgb rgb2oo rgbColor odfColor ); #----------------------------------------------------------------------------- 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' ); #----------------------------------------------------------------------------- BEGIN { *odfLoadColorMap = *ooLoadColorMap; *rgbColor = *oo2rgb; *odfColor = *rgb2oo; *getMasterPageElement = *getMasterPage; *getPageMasterElement = *getPageLayoutElement; *getPageMasterAttributes = *getPageLayoutAttributes; *createPageMaster = *createPageLayout; *updatePageMaster = *updatePageLayout; *pageMasterStyle = *pageLayout; } #----------------------------------------------------------------------------- # 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 isOutlineStyle { my $element = shift; return $element->hasTag('text:outline-level-style'); } 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 = ( part => '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 .= ('/style:' . $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\"]"; return $self->getNodeByXPath($context, $path); } } #----------------------------------------------------------------------------- sub getFontDeclarations { my $self = shift; my $context = $self->getFontDeclarationBody; my $path = $self->{'opendocument'} ? '//style:font-face' : 'style:font-decl'; return $self->getNodesByXPath($context, $path); } #----------------------------------------------------------------------------- sub getFontName { my $self = shift; my $fd = $self->getFontDeclaration(@_) or return undef; return $self->getAttribute($fd, 'style:name'); } #----------------------------------------------------------------------------- # 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 internal name or display name sub getStyleElement { my $self = shift; my $style = shift; return undef unless $style; return $style->isStyle ? $style : undef if ref $style; $style = $self->inputTextConversion($style); my %opt = (retry => 1, @_); if ($opt{'retry'}) { delete $opt{'retry'} unless (($opt{'retry'} eq 1) || ($opt{'retry'} eq 'true')); } my $root = undef; my $type = $opt{'type'} || 'style'; my $namespace = $opt{'namespace'} || 'style'; if ($opt{'category'}) { my $path = '//office:' ; if ($opt{'category'} =~ /^auto/) { $path .= 'automatic-styles'; } elsif ($opt{'category'} =~ /^named/) { $path .= 'styles'; } else { $path = $opt{'category'}; } $root = $self->getElement($path, 0); unless ($root) { warn "[" . __PACKAGE__ . "::getStyleElement] " . "Unknown search space\n"; return undef; } } my $attr = $self->{'retrieve_by'} || 'name'; my $xpath = "//$namespace" . ':' . "$type\[\@style:$attr\=\"$style\"\]"; my $e = $self->getNodeByXPath($xpath, $root); return $e if ((defined $e) || !$opt{'retry'}); if ($attr eq 'name') { $attr = 'display-name'; } elsif ($attr eq 'display-name') { $attr = 'name'; } else { return undef; } $xpath = "//$namespace" . ':' . "$type\[\@style:$attr\=\"$style\"\]"; return $self->getNodeByXPath($xpath, $root); } #----------------------------------------------------------------------------- sub getOutlineStyleElement { my $self = shift; my $level = shift or return undef; if (ref $level) { return $level->isOutlineStyle ? $level : undef; } my $xpath = "//text:outline-level-style\[\@text:level\=\"$level\"\]"; return $self->getNodeByXPath($xpath); } #----------------------------------------------------------------------------- sub updateOutlineStyle { my $self = shift; my $style = $self->getOutlineStyleElement(shift) or return undef; my %attr = @_; foreach my $k (keys %attr) { unless ($k =~ /:/) { my $v = $attr{$k}; delete $attr{$k}; $k = 'style:' . $k; $attr{$k} = $v; } } return $self->setAttributes($style, %attr); } #----------------------------------------------------------------------------- # get the name of the parent style, if any sub getParentStyle { my $self = shift; my $style = $self->getStyleElement(@_); unless ($style) { warn "[" . __PACKAGE__ . "::getParentStyle] Unknown style\n"; return undef; } return $self->getAttribute($style, 'style:parent-style-name'); } #----------------------------------------------------------------------------- # get the name of the primary ancestor # (returns the style name if it doesn't have any ancestor) sub getAncestorStyle { my $self = shift; my $style = $self->getStyleElement(@_); unless ($style) { warn "[" . __PACKAGE__ . "::getAncestorStyle] Unknown style\n"; return undef; } my $name = $self->styleName($style); my $parent_name = $self->getParentStyle($style); while ($parent_name) { $name = $parent_name; $style = $self->getStyleElement($name); $parent_name = $style ? $self->getParentStyle($style) : undef; } return $name; } #----------------------------------------------------------------------------- sub styleName { my $self = shift; my $p1 = shift; my $style = undef; my $newname = undef; if (ref $p1) { $style = $self->getStyleElement($p1) or return undef; $newname = shift; } else { my %opt = @_; $style = $self->getStyleElement($p1, %opt) or return undef; $newname = $opt{'newname'}; } $self->setAttribute($style, 'style:name', $newname) if $newname; return $self->getAttribute($style, 'style:name'); } #----------------------------------------------------------------------------- sub getAutoStyleList { my $self = shift; my %opt = ( namespace => 'style', type => 'style', @_ ); my $path = $self->{'auto_style_path'} . '/' . $opt{'namespace'} . ':' . $opt{'type'}; return $self->getElementList($path); } #----------------------------------------------------------------------------- sub getNamedStyleList { my $self = shift; my %opt = ( namespace => 'style', type => 'style', @_ ); my $path = $self->{'named_style_path'} . '/' . $opt{'namespace'} . ':' . $opt{'type'}; return $self->getElementList($path); } #----------------------------------------------------------------------------- sub getMasterStyleList { my $self = shift; my %opt = ( namespace => 'style', type => 'master-page', @_ ); my $path = $self->{'master_style_path'} . '/' . $opt{'namespace'} . ':' . $opt{'type'}; return $self->getElementList($path); } #----------------------------------------------------------------------------- sub getStyleList { my $self = shift; return ($self->getNamedStyleList(@_), $self->getAutoStyleList(@_)); } #----------------------------------------------------------------------------- sub styleProperties { my $self = shift; my $style = shift; my %opt = @_; my $namespace = $opt{'namespace'}; my $type = $opt{'type'}; my $path = $opt{'path'}; my $part_name = $opt{'-area'} || $opt{'area'}; my $element = $self->getStyleElement ( $style, namespace => $namespace, type => $type, category => $path ); return undef unless $element; delete $opt{'namespace'}; delete $opt{'type'}; delete $opt{'path'}; delete $opt{'-area'}; delete $opt{'area'}; my $change = undef; my $e_prefix = $element->getPrefix; my $tag_name = undef; unless ($part_name) { if ($e_prefix eq 'number') { $tag_name = 'number:number'; } elsif ($self->{'opendocument'}) { my $family = $element->att('style:family'); $tag_name = $family ? $e_prefix . ':' . $family . '-properties' : $element->name() . '-properties'; } else { $tag_name = 'style:properties'; } } else { $tag_name = $self->{'opendocument'} ? $e_prefix . ':' . $part_name . '-properties' : 'style:properties'; } my $properties = $self->getChildElementByName($element, $tag_name); my %attr = (); foreach my $k (keys %opt) { my $a = $k =~ /:/ ? $k : $e_prefix . ':' . $k; $attr{$a} = $opt{$k}; $change = 1; } if ($change) { $properties = $self->appendElement($element, $tag_name) unless $properties; $self->setAttributes($properties, %attr); } return $properties ? $self->getAttributes($properties) : undef; } #----------------------------------------------------------------------------- sub getStyleAttributes { my $self = shift; my $name = shift; my %style = (); my $element = $self->getStyleElement($name, @_); unless ($element) { warn "[" . __PACKAGE__ . "::getStyleAttributes] Unknown style\n"; return %style; } %{$style{'properties'}} = $self->styleProperties($element) if $self->styleProperties($element); %{$style{'references'}} = $self->getAttributes($element); return %style; } #----------------------------------------------------------------------------- sub getDefaultStyleElement { my $self = shift; my $style = shift; if (ref $style) { return ($style->getName eq 'style:default-style') ? $style : undef; } else { return $self->getNodeByXPath ("//style:default-style\[\@style:family=\"$style\"\]", @_); } } #----------------------------------------------------------------------------- sub getDefaultStyleAttributes { my $self = shift; my $style = $self->getDefaultStyleElement(@_); unless ($style) { warn "[" . __PACKAGE__ . "::getDefaultStyleAttributes] " . "No available default style in the context\n"; return undef; } return $self->getStyleAttributes($style, @_); } #----------------------------------------------------------------------------- # create a new style with given $name and %options # by default, the style is regarded as an 'named style' if $self is # 'styles.xml'but if $opt{path} or $opt{category} is 'auto', then # the style is inserted as an automatic style # if $self is a 'content.xml' object, the style is automatic sub createStyle { my $self = shift; my $name = shift; unless ($name) { warn "[" . __PACKAGE__ . "::createStyle] " . "Missing style name\n"; return undef; } my %opt = (check => 'false', @_); my $check = lc $opt{'check'} eq 'true'; delete $opt{'check'}; my $replace = lc $opt{'replace'} eq 'true'; delete $opt{'replace'}; if ($check || $replace) { my $old = $self->getStyleElement($name, %opt); if (defined $old) { unless ($replace) { warn "[" . __PACKAGE__ . "::createStyle] " . "Style $name exists\n"; return undef; } else { $self->removeElement($old); } } } my $element = undef; my $path = undef; my $type = $opt{'type'} || 'style'; my $namespace = $opt{'namespace'} || 'style'; my $context = $self->{'xpath'}; my $part_name = $self->getPartName; if ($part_name eq 'content') { $path = $self->{'auto_style_path'}; } elsif ($part_name eq 'styles') { $path = ($opt{'path'} && $opt{'path'} =~ /auto/) || ($opt{'category'} && $opt{'category'} =~ /auto/) ? $self->{'auto_style_path'} : $self->{'named_style_path'}; } else { warn "[" . __PACKAGE__ . "::createStyle] " . "Style creation is not allowed in the area\n"; return undef; } if ($opt{'prototype'} || $opt{'source'}) { my $p = $opt{'prototype'} || $name; delete $opt{'prototype'}; my $source = $opt{'source'} || $self; delete $opt{'source'}; my $proto = $source->getStyleElement($p, %opt); unless ($proto) { warn "[" . __PACKAGE__ . "::createStyle] " . "Unknown prototype style\n"; return undef; } $element = $proto->copy; } else { $element = $self->createElement($namespace . ':' . $type); } my $attachment = $self->getElement($path, 0); $element->paste_last_child($attachment); if ($type eq 'default-style') { $opt{'family'} = $name; } elsif ($type eq 'number-style') { $opt{'references'}{'style:name'} = $name; $opt{'family'} = 'data-style'; } else { $opt{'references'}{'style:name'} = $name; } delete $opt{'type'}; delete $opt{'namespace'}; delete $opt{'path'}; delete $opt{'category'}; $self->updateStyle($element, %opt); return $element; } #----------------------------------------------------------------------------- # set style attributes sub updateStyle { my $self = shift; my $style = shift; my %opt = @_; my $namespace = $opt{'namespace'}; my $type = $opt{'type'}; my $path = $opt{'path'} || $opt{'category'}; my $element = $self->getStyleElement ( $style, namespace => $namespace, type => $type, category => $path ); unless ($element) { warn "[" . __PACKAGE__ . "::updateStyle] " . "Unknown style\n"; return undef; } if ($opt{'prototype'}) { my $sv_name = $self->getAttribute($element, 'style:name'); my %proto = $self->getStyleAttributes($opt{'prototype'}); while (my ($key, $value) = each %proto) { if (ref $value) { while (my ($k, $v) = each %{$value}) { $opt{$key}{$k} = $v unless $opt{$key}{$k}; } } else { $opt{$key} = $value unless $opt{$key}; } } delete $opt{'prototype'}; $opt{'references'}{'style:name'} = $sv_name if $sv_name; } $opt{'references'}{'style:family'} = $opt{'family'} if $opt{'family'}; $opt{'references'}{'style:class'} = $opt{'class'} if $opt{'class'}; $opt{'references'}{'style:display-name'} = $opt{'display-name'} if $opt{'display-name'}; if ($opt{'next'}) { $opt{'references'}{'style:next-style-name'} = ref $opt{'next'} ? $self->styleName($opt{'next'}) : $opt{'next'}; } if ($opt{'parent'}) { $opt{'references'}{'style:parent-style-name'} = ref $opt{'parent'} ? $self->styleName($opt{'parent'}) : $opt{'parent'}; } $self->setAttributes($element, %{$opt{'references'}}); $self->styleProperties($element, %{$opt{'properties'}}) if ($opt{'properties'}); return $self->getStyleAttributes($element); } #----------------------------------------------------------------------------- # get a page layout descriptor (pagemaster) element. # the argument $page could be already a page layout; # if $page appears to be a master page (or master page name), the method # tries to get the linked page layout. sub getPageLayoutElement { my $self = shift; my $page = shift; my $name = undef; my $pagemaster = undef; my $l = $self->{'opendocument'} ? 'layout' : 'master'; my $layout_tag_name = 'style:page-' . $l; my $layout_key = $layout_tag_name . '-name'; my $layout_path = '//' . $layout_tag_name; if (ref $page) { # it is an element $name = $page->getName || ""; # is it pagemaster element ? if ($name eq $layout_tag_name) { # OK, return it return $page; } # is it a master page element ? elsif ($name eq 'style:master-page') { # yes, get the page master name $page = $self->getAttribute($page, $layout_key) or return undef; } } # here we have a name $pagemaster = $self->selectElementByAttribute ($layout_path, 'style:name', $page); return $pagemaster if $pagemaster; # it's not a page master name, # so we try it as a master page name my $masterpage = $self->selectElementByAttribute ('//style:master-page', 'style:name', $page) or return undef; # great! we got the master page, so get the page master name $name = $self->getAttribute($masterpage, $layout_key); # and cross the fingers return $self->selectElementByAttribute ($layout_path, 'style:name', $name); } #----------------------------------------------------------------------------- sub getPageLayoutAttributes { my $self = shift; my %attributes = (); my $pagemaster = $self->getPageLayoutElement(shift); unless ($pagemaster) { warn "[" . __PACKAGE__ . "::getPageLayoutAttributes] " . "Unknown page master\n"; return %attributes; } my $node = undef; %{$attributes{'references'}} = $self->getAttributes($pagemaster); %{$attributes{'properties'}} = $self->styleProperties($pagemaster); $node = $self->getStyleNode($pagemaster, 'background-image'); %{$attributes{'background-image'}} = $node ? $self->getAttributes($node) : (); $node = $self->getStyleNode($pagemaster, 'footnote-sep'); %{$attributes{'footnote-sep'}} = $node ? $self->getAttributes($node) : (); $node = $self->getStyleNode($pagemaster, 'header'); %{$attributes{'header'}} = $node ? $self->getAttributes($node) : (); $node = $self->getStyleNode($pagemaster, 'footer'); %{$attributes{'footer'}} = $node ? $self->getAttributes($node) : (); return %attributes; } #----------------------------------------------------------------------------- sub createPageLayout { my $self = shift; my $name = shift; my $layout_name = $self->{'opendocument'} ? 'page-layout' : 'page-master'; my %opt = ( category => 'auto', namespace => 'style', type => $layout_name, @_ ); my $pagemaster = undef; if ($opt{'prototype'}) { my $proto = $self->getStyleElement ($opt{'prototype'}, type => $layout_name); unless ($proto) { warn "[" . __PACKAGE__ . "::createPageMaster] " . "Improper prototype style\n"; return undef; } my $attachment = $self->getAutoStyleRoot; $pagemaster = $self->replicateElement($proto, $attachment); $self->setAttribute($pagemaster, 'style:name', $name); delete $opt{'prototype'}; } else { $pagemaster = $self->createStyle($name, %opt) or return undef; } delete $opt{'namespace'}; delete $opt{'type'}; delete $opt{'category'}; $self->updatePageMaster($pagemaster, %opt); return $pagemaster; } #----------------------------------------------------------------------------- sub updatePageLayout { my $self = shift; my $pagemaster = $self->getPageLayoutElement(shift) or return undef; my %opt = @_; if ($opt{'prototype'}) { my $sv_name = $self->getAttribute($pagemaster, 'style:name'); my %proto = $self->getPageLayoutAttributes($opt{'prototype'}); while (my ($key, $value) = each %proto) { if (ref $value) { while (my ($k, $v) = each %{$value}) { $opt{$key}{$k} = $v unless $opt{$key}{$k}; } } else { $opt{$key} = $value unless $opt{$key}; } } delete $opt{'prototype'}; $opt{'references'}{'style:name'} = $sv_name if $sv_name; } $self->setAttributes($pagemaster, %{$opt{'references'}}); delete $opt{'references'}; $self->styleProperties($pagemaster, %{$opt{'properties'}}); delete $opt{'properties'}; my %p = (); $p{'background-image'} = $self->setStyleNode($pagemaster, 'background-image'); $p{'footnote-sep'} = $self->setStyleNode($pagemaster, 'footnote-sep'); $p{'header'} = $self->setStyleNode($pagemaster, 'header'); $p{'footer'} = $self->setStyleNode($pagemaster, 'footer'); foreach my $k (keys %opt) { my $node = $p{$k} or next; my %parm = %{$opt{$k}}; my %attr = (); foreach my $name (keys %parm) { if ($name eq 'link') { $attr{'xlink:href'} = $parm{'link'}; } elsif (! ($name =~ /:/)) { $attr{"style:$name"} = $parm{$name}; } else { $attr{$name} = $parm{$name}; } } $self->setAttributes($node, %attr); } return $self->getPageLayoutAttributes($pagemaster); } #----------------------------------------------------------------------------- # switch page orientation (portrait -> landscape or landscape -> portrait) sub switchPageOrientation { my $self = shift; my $page = $self->getPageLayoutElement(shift); my %op = $self->styleProperties($page); my %np = (); $np{'fo:page-width'} = $op{'fo:page-height'}; $np{'fo:page-height'} = $op{'fo:page-width'}; my $o = $op{'style:print-orientation'}; if ($o) { if ($o eq 'portrait') { $np{'style:print-orientation'} = 'landscape'; } elsif ($o eq 'landscape') { $np{'style:print-orientation'} = 'portrait'; } } return $self->styleProperties($page, %np); } #----------------------------------------------------------------------------- # get the page content for a given page style sub getMasterPage { my $self = shift; my $name = shift; if (ref $name) { return $name->getName eq 'style:master-page' ? $name : undef; } else { $name = $self->inputTextConversion($name); return $self->getNodeByXPath ( "//style:master-page[\@style:name=\"$name\"]", $self->getRoot ); } } #----------------------------------------------------------------------------- # get/set the page master name of a given master page sub pageLayout { my $self = shift; my $masterpage = $self->getMasterPageElement(shift) or return undef; my $pagemaster = shift; my $ln = $self->{'opendocument'} ? 'layout' : 'master'; my $layout_key = 'style:page-' . $ln . '-name'; unless ($pagemaster) { return $self->getAttribute($masterpage, $layout_key); } else { my $pm_name = ref $pagemaster ? $self->getAttribute($pagemaster, 'style:name') : $pagemaster; $self->setAttribute($masterpage, $layout_key => $pm_name); return $pm_name; } } #----------------------------------------------------------------------------- # get the background image node in a given page master sub getBackgroundImageElement { my $self = shift; my $page = shift; my $pagemaster = $self->getPageLayoutElement($page); unless ($pagemaster) { my $masterpage = $self->getMasterPageElement($page) or return undef; my $name = $self->pageLayout($masterpage); $pagemaster = $self->getPageLayoutElement($name) or return undef; } return $self->getStyleNode($pagemaster, 'background-image'); } #----------------------------------------------------------------------------- # get/set a background image link sub backgroundImageLink { my $self = shift; my $page = shift; my $pagemaster = $self->getPageLayoutElement($page); unless ($pagemaster) { my $masterpage = $self->getMasterPageElement($page) or return undef; my $name = $self->pageLayout($masterpage); $pagemaster = $self->getPageLayoutElement($name) or return undef; } my $newlink = shift; my $node = $self->getStyleNode($pagemaster, 'background-image'); unless (defined $newlink) { return $node ? $self->getAttribute($node, 'xlink:href') : undef; } else { my $xpath = $self->_get_property_path ($pagemaster, 'background-image') . '[@xlink:href="' . $newlink . '"]'; return $self->makeXPath($pagemaster, $xpath); } } #----------------------------------------------------------------------------- sub getBackgroundImageAttributes { my $self = shift; my $node = $self->getBackgroundImageElement(@_) or return undef; return $self->getAttributes($node); } #----------------------------------------------------------------------------- # create or update a backgound image element associated to a given pagemaster sub setBackgroundImage { my $self = shift; my $page = shift; my $pagemaster = $self->getPageLayoutElement($page); unless ($pagemaster) { my $masterpage = $self->getMasterPageElement($page) or return undef; my $name = $self->pageLayout($masterpage); $pagemaster = $self->getPageLayoutElement($name) or return undef; } my %opt = ( 'style:position' => 'center center', 'style:repeat' => 'no-repeat', 'xlink:type' => 'simple', 'xlink:actuate' => 'onLoad', @_ ); my $node = $self->makeXPath ( $pagemaster, $self->_get_property_path ($pagemaster, 'background-image') ) or return undef; if ($opt{'link'}) { $opt{'xlink:href'} = $opt{'link'}; delete $opt{'link'}; } if ($opt{'import'}) { $self->importBackgroundImage($pagemaster, $opt{'import'}); delete $opt{'import'}; } $self->setAttributes($node, %opt); return $node; } #----------------------------------------------------------------------------- sub exportBackgroundImage { my $self = shift; my $source = $self->backgroundImageLink(shift) or return undef; $self->raw_export($source, @_); } #----------------------------------------------------------------------------- sub importBackgroundImage { my $self = shift; my $page = shift; my $pagemaster = $self->getPageLayoutElement($page); unless ($pagemaster) { my $masterpage = $self->getMasterPageElement($page) or return undef; my $name = $self->pageLayout($masterpage); $pagemaster = $self->getPageLayoutElement($name) or return undef; } my $filename = shift; unless ($filename) { warn "[" . __PACKAGE__ . "::importBackgroundImage] " . "No source file name\n"; return undef; } my ($base, $path, $suffix) = File::Basename::fileparse($filename, '\..*'); my $link = shift; my $fpath = $self->{'image_fpath'}; if ($link) { $link = $fpath . $link unless $link =~ /^$fpath/; $self->backgroundImageLink($pagemaster, $link); } else { $link = $self->backgroundImageLink($pagemaster); unless ($link && $link =~ /^$fpath/) { $link = $fpath . $base . $suffix; $self->backgroundImageLink($pagemaster, $link); } } $self->raw_import($link, $filename); return $link; } #----------------------------------------------------------------------------- sub createMasterPage { my $self = shift; my $name = shift; my $element = $self->getMasterPageElement($name); if ($element) { warn "[" . __PACKAGE__ . "::createMasterPage] " . "Master page $name exists\n"; return undef; } my %opt = @_; my $root = $self->getElement('//office:master-styles', 0); unless ($root) { warn "[" . __PACKAGE__ . "::createMasterPage] " . "No master styles space in the document\n"; return undef; } $opt{'style:name'} = $name; my $page_layout = $opt{'layout'} || $opt{'page-layout'} || $opt{'page-master'}; if ($page_layout) { my $ln = $self->{'opendocument'} ? 'layout' : 'master'; my $layout_key = 'style:page-' . $ln . '-name'; $opt{$layout_key} = $page_layout; delete $opt{'layout'}; delete $opt{'page-layout'}; delete $opt{'page-master'}; } if ($opt{'next'}) { $opt{'style:next-style-name'} = $opt{'next'}; delete $opt{'next'}; } return $self->appendElement ( $root, 'style:master-page', attribute => { %opt } ); } #----------------------------------------------------------------------------- sub masterPageExtension { my $self = shift; my $masterpage = $self->getMasterPageElement(shift) or return undef; my $position = shift or return undef; my $element = shift; my $tag = 'style:' . $position; unless ($element) { return $self->getNodeByXPath($masterpage, "//$tag"); } else { my $node = $self->makeXPath($masterpage, "/$tag"); return $self->appendElement($node, $element, @_); } } #----------------------------------------------------------------------------- sub masterPageHeader { my $self = shift; return $self->masterPageExtension(shift, 'header', @_); } sub masterPageFooter { my $self = shift; return $self->masterPageExtension(shift, 'footer', @_); } sub masterPageHeaderLeft { my $self = shift; return $self->masterPageExtension(shift, 'header-left', @_); } sub masterPageFooterLeft { my $self = shift; return $self->masterPageExtension(shift, 'footer-left', @_); } #----------------------------------------------------------------------------- sub getHeaderParagraph { my $self = shift; my $root = $self->masterPageHeader(shift) or return undef; my $n = shift; return $self->getElement('text:p', $n, $root); } #----------------------------------------------------------------------------- sub getFooterParagraph { my $self = shift; my $root = $self->masterPageFooter(shift) or return undef; my $n = shift; return $self->getElement('text:p', $n, $root); } #----------------------------------------------------------------------------- sub updateDefaultStyle { my $self = shift; my $style = $self->getDefaultStyleElement(shift); unless ($style) { warn "[" . __PACKAGE__ . "::updateDefaultStyle] " . "Unavailable default style in the context\n"; return undef; } return $self->updateStyle($style, @_); } #----------------------------------------------------------------------------- # remove a given style element (with element type checking) sub removeStyle { my $self = shift; my $element = $self->getStyleElement(@_); if ($element && $element->isStyle) { return $self->removeElement($element); } else { warn "[" . __PACKAGE__ . "::removeStyle] " . "Unknown style or non-style element\n"; return undef; } } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/OODoc/Text.pod0000644000175000017500000045125111355426650015227 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::Text - The text processing submodule of OpenOffice::OODoc =head1 DESCRIPTION This manual chapter describes the text-oriented methods of OpenOffice::OODoc, implemented by the OpenOffice::OODoc::Text class, and inherited by the OpenOffice::OODoc::Document class. These methods are not essentially dedicated to string processing; they are more precisely focused on text containers. A text container is a document element which can (and must) be used in order to support a text and integrate it at the right place and according to the right presentation rules. The OpenDocument specification defines a lot of such containers, and the present API supports many of them, such as paragraphs, headings, tables (or spreadsheets), lists, sections, and draw pages. Some of these containers can host other containers: for example, a table contains rows, a row contains cells, a section can contain almost everything including other sections, etc. These features are text-oriented, but can be used on documents of any class, such as spreadsheets or presentations as well as text documents. So, the 'Text' word doesn't mean that the features described in the present manual chapter are dedicated to OpenDocument Text (ODT) documents only. In the other hand, a few methods can't apply to any document class (ex: creating or retrieving draw pages makes sense with presentation and drawing documents only). OODoc::Text should not be explicitly used in an ordinary application, because all its features are available through the OpenOffice::OODoc::Document class, in combination with other features. Practically, the present manual is provided to describe the text-oriented features of OpenOffice::OODoc::Document (knowing that these features are technically supported by the OpenOffice::OODoc::Text component of the API). The OpenOffice::OODoc::Text class is a specialist derivative of OpenOffice::OODoc::XPath for XML elements which describe the text content of OOo/ODF documents. Here, "text content" means containers that can host text containers (i.e. tables, lists...) as well as flat text. Knowing that the "styles.xml" member of an OpenDocument file can contain text (because some style definitions, such as page headers or footers, can contain text), the presently described features can be used against this member as well as the "content.xml" member. This module should be used in combination with OpenOffice::OODoc::Styles, via the OpenOffice::OODoc::Document class, if the application has to handle detailed presentation parameters of text elements. This is because such parameters are held in styles elements and not in the text elements themselves, according to the principle of separation of content and presentation which is one of the foundations of the OpenDocument format. =head2 Methods =head3 Constructor : OpenOffice::OODoc::Text->new() Short Form: odfText() This constructor should not be explicitly used in ordinary applications knowing that all the features of the returned object are inherited by any Document object. See OpenOffice::OODoc::XPath->new for common arguments. Returns an OODoc::XPath OpenDocument connector with additional features mainly focused on text containers. This constructor is generally not explicitly called, knowing that it's automatically triggered each time a Document object is created. The XML member loaded by default is 'content.xml'. The most common creation method is like this: my $doc = odfText(file => 'my_file.odt'); This constructor should generally not be called directly, because it's inherited by odfDocument(). Other parameters can be supplied as options (see the properties list at the end of the chapter). Example: my %delim = ( 'text:h' => { begin => '\sect{', end => '}' }, 'text:list-item' => { begin => '\item' } 'text:footnote-body' => { begin => '\footnote{', end => '}' } ); my $doc = odfText ( file => 'filename.odt', paragraph_style => 'My Paragraphs', heading_style => 'My Headings', delimiters => { %delim } ); This technique gives the default styles to be used when creating new text elements. It also gives the particular delimiters (in this case LaTeX style markers) to be used at the beginning or end of some elements (in this case headings, list elements, footers) where the text is to be exported "as is". See the getText method of OODoc::Text for information about exporting text. =head3 appendDrawPage([options]) In a presentation or drawing document, appends a new page at the en of the document. Possible options are: name => page name (unique) id => page numeric ID (unique) style => page style name master => master page name Returns the new draw page element if successful, undef if not. =head3 appendHeading([options]) Creates a new heading of any level and appends it to the end of the document. Options are given as a hash [key => value]: 'text' => 'level' => heading level, default is 1 'style' => heading style, default is 'Heading 1' Examples: $doc->appendHeading(text => 'Next section'); adds the text 'Next section' as a level 1 heading. $doc->appendHeading ( text => 'Chapter Conclusion', level => '2', style => 'Heading_20_2' ); adds a level 2 heading to the end of the text body. 'Heading_20_n' styles, where 'n' is the level number, are presently available by default in OpenOffice.org. You can give any XML attribute to the new heading except for style or heading level. In this case, the program must construct a hash containing pairs of key-values for the attributes you want to create and pass it using the 'attribute' option. Example: my %attr = ( 'att1' => 'value1', 'att2' => 'value2' ); $doc->appendHeading ( text => 'Attributes are important', level => '1', style => 'Chapter heading', attributes => {%attr} ); If the 'text' option is empty, the heading is created with an empty content. Caution, creating headings with level attributes is not always sufficient to produce the needed result. For example, in order to generate headings with appropriate levels of numbering, each one must be attached to the right position in a hierarchy of lists, in combination with appendItemList(), insertItemList(), and appendListItem(). Note: this method can only be used with a new header i.e. it adds while it creates. To add an already available element using getHeading() from the same document or from another document, use the appendElement() method instead which is inherited from OODoc::XPath. =head3 appendItem(list [, text => text ,style => style ,[other_options]]) See appendListItem(). =head3 appendItemList([type => list_type, [style => style [, options]]]) Creates a new (empty) list and appends it to the end of the document. In OpenOffice.org 1 documents, an unordered list is the default, and if the 'type' option is given with the value 'ordered', then an ordered list is created. In Open Documents, the 'type' option is ignored because there are generic lists only (a list is ordered or "bulleted" according to a style, and not natively). The 'style' options controls the list's style (as opposed to each item's style). If absent, the list takes the default paragraph style (see appendParagraph). Like appendParagraph, this method actually creates a new list element. To copy an existing list in the same document or in another, use appendElement or replicateElement instead. =head3 appendListItem(list [, text => text ,style => style ,[other_options]]) Adds a new item to a list (ordered or unordered). The first argument is the existing list element (created using getOrderedList or getUnorderedList, for example). Options are the same as for appendParagraph. If the 'style' option is absent, the element is inserted according to the following rule: - if the new item is not the first one of the list, it takes the same style as the first item; - otherwise, it takes the default paragraph style of the document. The new item is created as a paragraph container by default. A 'type' option may be provided in order to require another type. Possible values are 'header', 'paragraph' or the XML name of any OpenDocument-compliant text container. If the type is provided and set to undef, the new item is created as an empty element, so it could/should receive a content later. An empty item could be used as the attachment point of another list, in order to create a hierarchy of lists. =head3 appendParagraph() Creates a new paragraph and appends it to the document. Options: 'text' => 'style' => An 'attribute' option is also available under the same conditions as for the appendHeading method (see above). If the 'text' option is empty, calling this method is the equivalent of adding a line feed. If the 'style' option is empty, the style from the 'paragraph_style' property of the OODoc::Text instance is used. By default, the new paragraph takes place at the end of the document. But it's possible to attach it as the last child of an existing text container (ex: a table cell). To do so, the container must be provided through an 'attachment' option. For example, to append a new paragraph in a table cell, one can write my $cell = $doc->getTableCell("Table1", "B12"); $doc->appendParagraph ( text => "The cell, reloaded", attachment => $cell ); Note: this method can only be used with a new paragraph i.e. it adds while it creates. To add an already existing paragraph using getParagraph from the same document or from another document, use the appendElement, insertElement or replicateElement methods instead which are inherited from OODoc::XPath. Note: The repeated spaces are not properly processed, so any sequence of spaces (whatever its length) in the 'text' string is replaced by a single space in the target document. See setText() and extendText(). =head3 appendRow(table [, options]) Appends a row to the end of the given table either by reference, by logical name or by sequential number. By default, the new row is simply an exact copy of the preceding row (in terms of content and presentation). You can pass an options hash which will give certain attributes to the created row, under the same conditions as for the appendElement method of OODoc::XPath. The returned value is the created row element. Example: open SRC, '<', 'data.txt'; my $table = $doc->getTable("Table1"); my ($h, $l) = $doc->getTableSize($table); for (my $i = 0 ; my $record = ; $i++) { last unless $record; chomp $record; my @data = split ';', $record; my $row = $i < $h ? $doc->getRow($table, $i) : $doc->appendRow($table); for (my $j = 0 ; $j < $l ; $j++) { $doc->cellValue($row, $j, $data[$j]); } } The above program reads a CSV format data file sequentially (one record per line, comma-separated fields). Each record is split and put into a row in table Table1. On reading each new record, the reference for the following row is loaded by getRow, until the total number of rows is reached (total obtained previously using getTableSize). If the table is already full, it is lengthened by a row using appendRow. The internal loop loads the read data into the row's cells (pre-existing or newly created). See the sections on getTable, getRow, getTableSize and cellValue for a better understanding of this example. However, if good performance is what you are after, massive repetition of this method is not recommended (e.g. for lengthening a table dynamically, row by row, whilst loading external data into it). Rather than running dozens or hundreds of successive appendRows, it would be better for the application to read the total number of records to be loaded (using, for example, select count if from a relational database or otherwise preloading the data into an ordinary Perl table) and create a table of appropriate size in advance using insertTable() or appendTable(). =head3 appendSection(name [, options]) Creates a new section with the given name, and appends it by default to the end of the document body. If the "attachment" option is provided, with an existing element as its value, the new section is appended in the context of this element. For example, if the value of "attachment" is an existing section, the new section is appended as the last sub-section of the existing one. A section may be used either to hold a local content or to insert a subdocument which can be reached through an external link. In order to insert a subdocument link instead of an ordinary section, the application must provide a "link" option whose value is either a local file path or an URL. Example: $doc->appendSection ( "Article", link => "http://mycompany.com/doc/article.odt" ); Other possible options: 'style' allows the application to explicitly select a style for the new section 'protected' write-protects the section when the document is edited; "true" or "false", default "false" 'key' in combination with "protected" => "true", write- protects the section by password (the value of "key" is not the real password, but an encrypted password, so the end-user will never remove the protection by simply typing the key as it is written in the program); see lockSection(), unlockSection() and sectionProtectionKey() =head3 appendTable(name, rows, columns [, options]) Creates a new table with the given name, number of rows and number of columns, and appends it by default to the end of the document body. The name must be unique within the document (the call is rejected if the name already exists). Returns the created table element if successful. Beware: Creating simple tables from scratch is very easy; however, for a realistic application, it's strongly recommended to replicate XML table templates previously created with an ODF-compatible editing software. A reasonably sophisticated table implies dozens of style definitions and would require a lot of perl code and a deep knowledge of the ODF specification, while it could be created in a few minutes through a WYSIWYG tool. 'rows' and/or 'columns', if omitted, are replaced by the 'max_rows' and 'max_cols' properties of the document (see the properties below). By default, the table is set to fit the entire width between the left and right margins with equal sized columns, cells of type string and without borders or background colour. Possible options: 'table-style' => table style 'cell-type' => default cell type 'cell-style' => default cell style 'text-style' => default cell text style The first option is the name of a table style which defines certain global properties for the table (width, background colour, etc.). See the OpenOffice::OODoc::Styles manual for information about styles. The second option is the cells' default data type. The main types available are string, float, currency, date, percentage. Caution: to be properly treated as having a numeric format in OOo/ODF, a cell needs more than to be just marked 'numeric'. If the cell really needs to be treated properly as a number, you must also give it a cell style which itself refers to a number style. The cell-style parameter can do this. However, even though the OODoc::Styles module is there to otherwise help you create and add styles from a program, this type of exercise can become very labour-intensive. We therefore recommend using basic tables created in advance from document templates or style libraries created from an office application, rather than creating complex number tables from code. The text-style option selects the paragraph style applicable to the text displayed in each cell. Once the table is created, you can obviously modify each cell's type and style individually. Example: my $table = $doc->appendTable ( "Rate", 22, 5, 'table-style' => 'Table1', 'text-style' => 'Text body' ); =head3 appendTableRow(table) See appendRow. =head3 autoSheetNormalizationOff() Deactivates the automatic sheet normalization. See autoSheetNormalizationOn(). =head3 autoSheetNormalizationOn('full') =head3 autoSheetNormalizationOn(height, width) Activates the automatic normalization of any used table. This method instructs the API to automatically normalize anything table or sheet as soon as it's reached through getTable() or another table-related access method. The automatic normalization is not activated by default. It can be deactivated at any time using autoSheetNormalizationOff(). See normalizeSheet() for details about the arguments and the effects. =head3 bibliographyEntryContent(id [, key1 => value1, key2 => value2, ...]) Gets, and optionally sets, the properties of a given (existing) bibliographic entry. The optionally updated properties are provides as a hash. The returned description is a hash. The first argument can be either the logical identifier of the entry (as it appears for the end-user) or a previously found bibliography entry element (see getBibliographyElements()). Example: my %desc = $doc->bibliographyEntryContent ( "GEN99", author => 'Genicorp', pages => 62 ); This sequence updates the "Author" and "Pages" values of the "GEN99" entry, then returns all the content of the entry in %desc. Caution: Several bibliography entries can have the same identifier. This method processes one element at a time. In the example above, only the first occurrence of the "GEN99" entries is updated. So, if the user needs to ensure that all the entries with the same identifier have the same content, the appropriate code should be something like: my @entries = $doc->getBibliographyElements("^GEN99$"); foreach my $entry (@entries) { $doc->bibliographyEntryContent ( $entry, author => 'Genicorp', pages => 62 ) } Caution: This method allows the user to create any new property and to put any value in any property, without control. For information about the legal and/or recommended properties, see the OpenDocument specification and the OpenOffice.org bibliographic project (http://bibliographic.openoffice.org). =head3 bookmarkElement(element, name [, offset]) See setBookmark(). =head3 cellCurrency(table, row, column [, currency]) =head3 cellCurrency(cell [, currency]) Get/set the currency unit of a cell. If a currency is provided, the cell value type is automatically switched to 'currency'. =head3 cellFormula(table, row, column [, formula]) =head3 cellFormula(cell [, formula]) Accessor which returns the formula (or function) contained in the given table cell. Returns undef if no formula is found in the cell. The cell address is the same as for getCellValue(). If a formula is given as the last argument, it is put into the cell, overwriting any existing formula. No check of the syntax is carried out on the inserted formula. It is up to the application to insert a formula which conforms to OOo/ODF syntax. Example: $doc->cellFormula(1,3,2, "sum "); Note 1: inserting or replacing a formula does not directly modify the value or text of the cell. Proper interpretation of a formula does not happen until the fields are updated when the document is reloaded into the office software. Note 2: syntax and functionality of cell formulae differ greatly between office applications. =head3 cellSpan(table, row, column [, hspan [, vspan]]) =head3 cellSpan(cell [, hspan [, vspan]]) In a spreadsheet document, get/set the span of a table cell, knowing that this span can be one or more columns. The cell addressing is the same as with getTableCell(). Example: $doc->cellSpan($table, "B4", 3); creates a 3-cell span from B4 in a spreadsheet. With only one span argument, this method works for horizontal, left to right expansion. With an additional argument, the expansion is bi- directional, covering one or more rows below the given cell. The horizontal span should be set to 1 in order to get a vertical span only. The text of the covered cells (if any) is concatenated to the original content of the expanded cell (as in OOo Writer or Calc). The user should make sure that the cell expansion will not invade the span of another, previously expanded cell. Assuming A is a the target of cellSpan(), B is an existing expanded cell, and C is a covered cell in the span of B, the following rules apply: If B is to be covered by the span of A, the span of B is automatically reset to 1, so C becomes visible, then B is covered by A. But if C is in the target range of cellSpan() while B is not, the method produces an inconsistency in the table (this inconsistency doesn't prevent OpenOffice.org and KSpread from loading the file but the span of A is just ignored). In list context, the method returns the horizontal span, then the vertical span. In scalar context, it returns the horizontal span only. Caution: when related to table cells, "span" has not the same meaning as when related to flat text (see getSpan() and setTextSpan()). =head3 cellStyle(table, row, column [, stylename]) =head3 cellStyle(cell [, stylename]) Get or set the style of a table cell. =head3 cellValue(table, row, column [, value [, text]]) =head3 cellValue(cell [, value [, text]]) Without the "value" argument: see getCellValue(). With "value" (and, optionally, "text"): see updateCell(). =head3 cellValueType(table, row, column [, type]) =head3 cellValueType(cell [, type]) Get/set the data type of a table cell. Possible value types are 'string', 'float', 'percentage', 'currency', 'date', 'time', 'boolean'. Note: If an application must convert a 'string' cell to a numeric one and fill it with a numeric value, cellValueType() must be called *before* cellValue(). Ex: my $cell = $doc->getTableCell('Sheet1', 4, 8); $doc->cellValueType($cell, 'float'); $doc->cellValue($cell, 12.34); =head3 checkIndexMark(name, type [, context]) Checks the existence and validity of an index mark (see setIndexMark() for details about range index marks). The mandatory argument are the index entry identifier and the index entry type (namely 'toc' or 'alphabetical index'. A context element may provided in order to restrict the search context. This method may return 1, 0 or undef. '1' means that the index mark is present and consistent; '0' means that the index mark is present in the context but not valid; undef means that the index mark doesn't exist in the context. If the result is 0, the are 2 possible reasons: the start point or the end point of the index mark has been found, but not both, or both have been found but there relative positions are wrong (the end is located before the start). Whatever the explanation, this result means that some cleaning should be done (see deleteIndexMark()). =head3 checkRangeBookmark(name [, context]) Checks the existence and validity of a range bookmark (see setBookmark() for details about range bookmarks). The mandatory argument is the bookmark name. A context element may provided in order to restrict the search context. This method may return 1, 0 or undef. '1' means that the range bookmark is present and in the right order; '0' means that the bookmark is present in the context but not valid; undef means that the bookmark doesn't exist in the context. If the result is 0, the are 2 possible reasons: the start point or the end point of the range has been found, but not both, or both have been found but there relative positions are wrong (the end is located before the start). Whatever the explanation, this result means that some cleaning should be done (see deleteBookmark()). =head3 columnStyle(column_element [, style]) =head3 columnStyle(table, column [, style]) Returns the style name of the given column or replaces it with a new one. A column can be indicated either directly by reference or by the pair [table, column number]. The table itself can be indicated either by a table element, its number or its logical name. If the 'style' argument is given, it replaces the old column style. Giving a column a style is actually the only way to control the width of a column in a table. Example: $doc->columnStyle('Table1', 2, 'NewStyle'); Caution: columns are numbered beginning at 0. =head3 copyRowToHeader(table, rownum) =head3 copyRowToHeader(row) This method appends a copy of a given table row to the header of the table. It may be called repeatedly, allowing multi-row header creation. A table header is a row, or a sequence of rows, that is displayed at the top of a table and repeated at the top of every page if the table is spanned across more than one page. The given row remains in place unchanged; it's used as a template for the new header row. =head3 createParagraph([text [, style]]) Creates a free paragraph for later use. Unlike appendParagraph() or insertParagraph(), this method doesn't attach the new paragraph to the document. Without arguments, the paragraph is created empty. The first argument, if any, provides the text content of the paragraph. The second one, if any, is regarded as the style name; the default style is "Standard". =head3 createTextBox(options) Creates a new text box. Can apply to any document class, but mostly used in presentations or drawings (where text boxes are required to host text content). Text boxes are implemented through frame element, so you should see createFrame() in the OpenOffice::OODoc::XPath manual chapter in order to understand the meaning of every option. The following options are allowed (and generally required in order to make a text box really visible and properly rendered): page: the page where the box must be attached; in presentations or drawings, this option should be set with the page name; name: the (unique) name of the text box; size: the size of the box; position: the page-relative position; style: the graphic style of the box; like an image box, a text box often requires a style to be properly displayed; content: the content to be displayed in the box; if this option is set to a literal, the given content is inserted as a paragraph in the box; if the given value is the reference of an element, this element is attached as is in the box (so it's possible to insert any complex object, such as a table, an item list, etc). The method returns the reference of the new text box element. The example below creates an graphic style ("TB"), then a text box ("The Box") which uses the new style. See O::O::Styles for comments about createStyle(). The text box is attached in a presentation page identified by its name ("AnyPageName"). The size (width then height) and position (x, y) options are provided in centimeters (other units are allowed), each one in a single string. $doc->createStyle ( "TB", family => "graphic", parent => "objectwithshadow", properties => { 'style:vertical-pos' => 'from-top', 'style:horizontal-pos' => 'from-left', 'style:vertical-rel' => 'page', 'style:horizontal-rel' => 'page' } ); $doc->createTextBox ( page => "AnyPageName", name => "The Box", size => '12cm, 4cm', position => '8cm, 14cm', style => 'TB', content => "The text in the box" ); In this example, the content option is set to a flat text, so it will be inserted as a standard paragraph. If we want to insert a paragraph with a non-default style, this option must be set to the reference of an existing paragraph (which may have been created using createParagraph() or copied from another place). =head3 defaultOutputTerminator([chars]) Get or set the default terminator character for text export. Example: $doc->defaultOutputTerminator("\n"); After this instruction, a line-break will be appended at the end of every paragraph or header exported by getText(), selectTextContent() or other text extracting methods. To reverse this behaviour, the user can call this method with an empty string. Without argument, returns the currently selected terminator, if any. =head3 deleteBookmark(name [, context]) Deletes the bookmark owning the given name (if defined). A context optional argument is provided; if so, the bookmark is deleted only if it's located in the given context. If several bookmarks wrongly own the same name, they are removed. The method returns the number of physical deleted elements, i.e. 1 for a regular position bookmark, 2 for a range bookmark, 0 for nothing. Any other return value means that deleteBookmark() has cleaned up a strange situation (ex: more than one bookmark for a single name, a position bookmark with one start and many ends, and so on). Warning: if the context argument is set (or if the default current context is not the whole document), and if the bookmark to be deleted is not entirely included in the context, the result may be a partially deleted bookmark (wrong). See also deleteBookmarks(). =head3 deleteBookmarks([context]) Delete all the bookmarks in the current context (by default the whole document) or in a given optional context, and returns the number of physical deleted elements (that may be greater than the number of deleted bookmarks, knowing that a bookmark may be stored a one or two XML elements). Warning: some inconsistencies may result if the context is not the whole document, knowing that a range bookmark could run across the border of the restricted context. See also deleteBookmark(). =head3 deleteColumn(table, col_num) =head3 deleteColumn(col_elt) Deletes a given column in a given table. Caution: Before using this method, the application should ensure that the whole area from the beginning of the table to the last cell of the column to be deleted is "normalized". See normalizeSheet() for details about table normalization. =head3 deleteHeading() See removeHeading(). =head3 deleteRow(table, row_num) =head3 deleteRow(row_elt) Deletes a given row in a table. =head3 deleteIndexMark(id [, type [, context]]) Deletes the index mark owning the given identifier (if defined). The first argument (mandatory) is the index mark identifier. The second argument is the index type ('alphabetical index' or 'toc', the first one is the default). A context optional argument is provided; if so, the index entry is deleted only if it's located in the given context. If several index marks wrongly own the same identifier, they are removed. The method returns the number of physical deleted elements, that should be 0 (if the index mark did not exist) or 2 (the start and the end points). Any other return value means that deleteIndexMark() has cleaned up a strange situation (less or more than two range delimiters). See also deleteIndexMarks(). =head3 deleteIndexMarks([type [, context]) Without argument, deletes all the TOC marks and alphabetical index marks in the document (or the default context). The first argument, if set, non-blank and non-empty, restricts the deletion to one type of index marks; it should be either 'toc' or 'alphabetical index' (unless the user need to remove non standard index marks). A particular context may set through the third argument in order to restrict the index mark removal to the content of a particular element. See also deleteIndexMark(). =head3 drawPageId(page [, new_id]) Returns the internal identifier of a presentation page, and changes it if a second argument is provided. The page id is a positive integer. The first argument must comply to the same rules as with getDrawPage. =head3 drawPageName(page [, newname]) Returns the visible name of a presentation or drawing page. The first argument can be a page order number, a page element or the present page name (see getDrawPage). The page is renamed if a second argument is provided. Example: $doc->drawPageName("oldname", "newname"); =head3 deleteTableColumn(table, col_num) See deleteColumn(). =head3 deleteTableRow(table, row_num) See deleteRow(). =head3 expandSheet() Synonym of expandTable(). =head3 expandTable(table, height, width) Increases the size of the given table or spreadsheet. This method silently executes a full normalization of the table before resizing it. See normalizeSheet() for details about this operation. This method is specially useful in order to ensure the availability of an appropriate workspace in a spreadsheet whose apparent size is almost unlimited through the GUI of a typical desktop software but but whose real size is unknown and/or doesn't include all the target area of the application. The vertical expansion is implemented by repetitive replication of the last row, while the horizontal expansion is implemented by repetitive replication of the last cell of the last row. So the new cells in the right side are copies of the old bottom-right cell, while the new rows are copies of the old last row. Any size argument which is not larger than the previous height or width is silently ignored, so method produces the same effect as normalizeSheet() with the "full" parameter. The return value is the table element itself in scalar context, or the table size in array context. Note that there is no direct method to shrink a table. However, it's possible to do the job by deleting selected rows and or columns through deleteTableColumn() and/or deleteTableRow(). =head3 extendText(element, text [, style [, offset]]) Inserts the text provided as the second argument into the element specified by the first argument. The second argument may be either a flat string or another existing text element. If the 'text' argument is a paragraph or heading element, the text content (and not the element itself) is inserted. But if 'text' is any other element (for example: a variable text field or a sequence of spaces), its inserted as is. This method is an improvement of the general extendText() method which is documented in the OpenOffice::OODoc::XPath manual page. If a third argument is provided and is neither 0 nor an empty string, it's regarded as the desired style of the new text, which is inserted as a "styled span" (see setTextSpan() for details about text "spans"). By default, the text is inserted without any special style (i.e. with the same style as the containing element). The new text is, by default, appended to the existing content of the element. However, if a valid numeric value is provided as the fourth argument, the new text is inserted within the existing content, at the given offset. If the offset is negative, it's counted backwards from the end of the string. If it's set to 0, the insertion takes place at the beginning. $doc->createStyle ( "BlueYellow", family => "text", properties => { "fo:color" => odfColor("blue"), "fo:background-color" => odfColor("yellow") } ); my $p = $doc->getParagraph(4); $doc->extendText($p, "New text", "BlueYellow", 5); In the example above, "New text" is inserted at the offset 5 within the 5th paragraph, in blue letters on a yellow background. Of course, the offset argument can't be passed unless the style one is present. However, in order to pass an offset without setting a style, the application has just to provide a 0 or an empty string as the third argument. Example: $doc->extendText($p, "New text", "", 5); Caution: the use of extendText() with the style and offset optional arguments is allowed for some simple situations; however, there are more powerful methods to insert additional text, with or without a particular style, within en existing element. See updatetext() and setTextSpan(). =head3 getBibliographyElements([id]) Returns the list of the bibliographic entry elements contained in the document. If an argument is provided, the returned list is restricted to the bibliographic entries matching it (this argument can be a regexp). Example: my @biblio = $doc->getBibliographyElements("^W3C"); returns the bibliographic entries where the identifier begins with "W3C". =head3 getBookmark(name) Returns the bookmark element (if defined) corresponding to the given bookmark name. If the bookmark covers a range of text (i.e. if it's not a position), the returned element is the "bookmark start" one. =head3 getCell() Synonym of getTableCell(). =head3 getCellParagraph(table, row, column) =head3 getCellParagraph(cell) Returns the paragraph element contained in a given table cell, if the cell contains a paragraph. If the cell contains more than one paragraph, returns the first one. =head3 getCellParagraphs(table, row, column) =head3 getCellParagraphs(cell) Returns the list of the paragraph elements contained in a given table cell (knowing that a single cell can contain one or more paragraphs). =head3 getCellPosition(cell) Returns an array corresponding to the zero-based, numeric coordinates of a table cell in a document, which can be used later to retrieve a cell at the same location through getCell(). The return values represent, in this order, the table, the row and the column. The header rows of the table, if any, are not counted. Example: my @coord = $doc->getCellPosition($cell); A triplet such as (2, 4, 9) tells that the cell is located at the 10th position in the 5th row of the 3rd table of the document. In scalar context, this method returns nothing more than the first element of the triplet, i.e. the zero-based position of the table in the order of the document. However, if the real need is to retrieve the table element itself, $cell->parent->parent is more efficient. This method produces a warning and returns undef if the argument is not a table cell. Caution: getCellPostion(), like any other accessor using object coordinates related, works only with normalized tables. =head3 getCellValue(table, row, column) =head3 getCellValue(cell) Returns the value of a table cell, if the cell is defined and uncovered. Caution, in order to get the cell element itself for further processing, use getCell() instead. The first form indicates a cell by its 3D coordinates, as with getCell(). The second form (quicker) takes a cell element as its only argument (e.g. as returned by a previous getCell call). This method behaves in two different ways depending on the cell type. The displayable text of the cell is regarded as the cell value if the cell type is 'string'. If the cell type is one of the possible numeric types ('float', 'currency', 'date'), the returned value is the internal, numeric value. This difference in handling is designed to allow programs to use returned numeric values directly in calculations. See also cellValueType(). Note: To get information about a cell other than its value or value type (numeric, etc.), the best way is first to get its element reference with getCell() and then use it with getAttribute. =head3 getChapterContent(heading [, options]) This method returns the list of the elements depending (from the end-user's point of view) on a given heading element, not including the heading element itself. The argument and the options are the same as with getHeading(). Examples: my @list = $doc->getChapterContent(2, level => 3); foreach my $element (@list) { my $text = $doc->getText($element); print "$text\n"; } The code above selects and prints all the text elements below the third level 3 heading of the document (not including the content of the header itself. The following example creates a new section whose content is made of a heading and the content of the depending chapter (the heading text is used as the section name): my $heading = $doc->getHeading(2, level => 3); my $heading_text = $doc->getFlatText($heading); my $section = $doc->appendSection($heading_text); my @content = $doc->getChapterBodyElements($heading); $doc->moveElementsToSection($section, $heading, @content); (See appendSection(), getHeading(), moveElementsToSection() in the present manual chapter, and getFlatText() in OpenOffice::OODoc::XPath) Caution, this method returns a list of elements and not an element. Chapters, unlike sections, are not defined in OpenDocument. So, getChapterContent() should be used as a possible workaround in order to isolate a logical set of content elements which is not packaged in a section. =head3 getColumn(table, column) Returns the element reference of the given column in the given table. The first argument is either the table's sequential number in the document, logical name or element reference. The second argument is the column's number in the table. Synonym: getTableColumn. Caution: The application should ensure that the area including the needed column is "normalized". See normalizeSheet() for details about table normalization. =head3 getDrawPage(pos/name) For presentation and drawing documents. Returns the element reference of the given page name or position. If the argument contains an integer, the page is selected according to its zero-based position. If the value is negative, the position is counted backwards from the end. If the argument is alphanumeric, it's regarded as a page name, and the page is selected accordingly. Caution: This method can't retrieve a page by name if the name contains numeric characters only; selectDrawPageByName() should be preferred to do so. =head3 getEndnoteCitationList() Returns the list of all the endnote citations (i.e. references to footnotes included in the text) contained in the document. =head3 getEndnoteList() Returns the list of all the endnote body elements contained in the document. Should be replaced by getNoteList() with the "class" option set to "endnote". =head3 getFootnoteCitationList() Returns the list of all the footnote citations (i.e. references to footnotes included in the text) contained in the document. =head3 getFootnoteList() Returns the list of all the footnote body elements contained in the document. Should be replaced by getNoteList() with the "class" option set to "footnote". =head3 getHeading(n [, options]) Returns the nth+1 heading element. If n is negative, headings are counted backwards from the last. getHeader(-1) returns the last heading element of the document. The only one possible option is "level". It allows the application to select the nth+1 heading element for a given level. Example: my $heading = $doc->getHeading(2, level => 3); selects the third level 3 heading in the whole document. See also getChapterContent(). Caution: without the "level" option, this method counts sequentially through all headings along a single plane, irrespective of their level. E.g. if you have a level 1 heading then two level 2 headings then a level 1 heading, the call getHeading(3) returns the last level 1 heading. =head3 getHeadingList([level => value]) Returns a list of heading elements (i.e. elements called 'text:h' in the document body). If the "level" option is provided, the list is restricted to the headings having the given level. =head3 getHeaderRow(table [, row_number]) See getTableHeaderRow(). =head3 getHeadingText(n) Returns the text of the nth+1 heading element. Elements are counted in the same way as for getHeading(). =head3 getHeadingTextList() Returns a list of document heading texts. In a list context, the result is returned in the form of a list of character strings. In a scalar context, the result is a single string in which the headings are separated by a line-break character ("\n"). Note: This list is "flat". It contains no information about the headings' hierarchy. To get a hierarchical contents list, you must start with the list of headings obtained using getHeadingList and check each element's level attribute ('text:level'). =head3 getItemElementList(list) Returns a list of elements which represent items of an ordered or unordered list. The argument is a "list" element (obtained previously e.g. using getItemList, getOrderedList or getUnorderedList). Each element in this list can be used with item handling methods. =head3 getItemList(n) Returns the element which represents the nth+1 list in a document if found. WARNING: In the OpenOffice.org 1 documents, only "ordered lists" and "unordered lists" can be present. In the Open Document format, there are generic list objects only, and each one is made "ordered" or "unordered" by its style. So, this method will never return anything from an OOo 1 document. =head3 getLevel(element) See getOutlineLevel(). =head3 getList(n) See getItemList(). =head3 getListItem(list, n) Returns the nth+1 item in a given list if found. The list (1st argument) may be given either by its order number in the document, or directly as an element reference. =head3 getNoteCitationList() For OpenDocument only (doesn't work on old OpenOffice.org documents). Returns the list of all the note citation elements (whatever their note class, i.e. "endnote" or "footnote"). =head3 getNoteClass(note_element) Returns the class of the given note element. Possible values are presently "endnote" and "footnote". Returns undef unless the given element is a note. =head3 getNoteElement(class => $note_class, citation => $note_citation) Returns the first note element matching the given class and citation, if any. Returns undef if the target note element doesn't exist. The "class" parameter is either "endnote" or "footnote". The "citation" parameter is the numeric or literal which refers to the note, as it's visible for the end user. Caution: The uniqueness of a note citation in a given note class is not a general rule. The citation is an identifier when it belongs to an ordered sequence (such as 1, 2, 3... or "i", "ii", "iii"...). But the author is allowed to use the same citation (ex: an asterisk) for more than one footnote or endnote. In such a situation, the method returns the first note matching the given citation and the given class. As a consequence, the note identifier, if known, is a better option (see the second form of getNoteElement()). =head3 getNoteElement(id => $note_identifier) Returns the note element matching the given internal note identifier (which is a "text:id" attribute according to the ODF specification). This internal identifier is unique, whatever the note class, so the "class" parameter is not needed. However, "class" may be provided as an additional filter; if so, the method will return undef if the element matching the identifier doesn't match the class. =head3 getNoteElementList() Returns the list of the endnote and footnote main elements. =head3 getNoteList() Returns the list of the endnote and footnote body elements. =head3 getOrderedList(n) Returns the element which represents the nth+1 ordered list in a document if found. WARNING: Ordered lists are possible in the OpenOffice.org 1 format only. Don't use it against OpenDocument. =head3 getOutlineLevel(element) Returns the level number of a text element, or undef if the given element don't have a level number. Every heading element should have a level, while ordinary text body elements should not. Example: my $level = $doc->getOutlineLevel($element); if ($level) { print "There is a level $level heading\n"; } else { print "Non-heading element\n"; } =head3 getParagraph(n) Returns the nth+1 paragraph in the document body, or undef if the given number is greater than or equal to the total number of paragraphs in the document. You can also pass a negative argument, in which case paragraphs are counted backwards from the end (-1 being the last paragraph). By paragraphs we mean 'text:p' elements, which excludes headers but includes non-empty table cells, contents of list items and footnotes. Returned value is an element and not the text of the paragraph. All read/write operations involving attributes and content can use this element. =head3 getParagraphList() Returns a list of paragraph elements (i.e. 'text:p' elements in the document body). =head3 getParagraphText(n) Returns the text of the nth+1 paragraph, counted using the same rules as for getParagraph. =head3 getParagraphTextList([filter]) Returns a list of texts contained in the paragraphs of a document ('text:p' elements). A filter can be passed as an optional argument (literal or regular expression). In this case, only paragraph texts whose content match the filter are returned. In a list context, the result is returned in the form of a list of character strings. In a scalar context, the result is a single string in which the paragraphs are separated by a line-feed character ("\n"). =head3 getRow(table, row_num) Returns the element reference which corresponds to a row in a table. The first argument is either the table's sequential number in the document, logical name or element reference. The second argument is the row number in the table. Synonym: getTableRow. This methods ignores the table header (if any). It can retrieve a row in the table body only. See getTableHeaderRow(). =head3 getRowCells(table, row) =head3 getRowCells(row) Returns the list of the uncovered cell elements corresponding to a given table row. The row can be provided either by table ID and row number or by direct row object. =head3 getSection(name/number) If the first argument is a number, returns the nth+1 section in a document (section numbers are zero-based; if the argument is negative, the sections are counted from the end). The second form allows you to select a section by its logical name (as it would appear to the end user when editing the section's properties). This name is obviously easier to use than a number. Moreover, this type of selection means the application will still work even if a section changes position within a document. The returned object is a "handle" that can be used for subsequent element creations or retrievals in the selected section. =head3 getSpanList([context]) Returns a list of elements, in the given context, which correspond to texts which "stand out" from the regular flat text, i.e. which have been given a style which makes them stand out from the rest of the paragraph containing them. The context may be a paragraph, a section, or any other text container. The context argument is optional; the default context is the whole document. For example, a word in italics or in font size 12 in a paragraph of mostly standard characters in font size 10 is a 'span' element and would therefore appear in a list returned by getSpanList(). =head3 getSpanTextList([filter]) Gets a list of texts which "stand out" in the same way as getSpanList() and returns it under the same conditions as getParagraphTextList() or getHeadingTextList(), with optional filter. =head3 getStyle(path, position) =head3 getStyle(element) Obsolete. See textStyle(). =head3 getTable(number_or_name [, 'normalize']) =head3 getTable(number_or_name [, length, width]) Returns the reference of a table, selected by name or number, in a scalar context. In an array context, returns the table size, like getTableSize(). This method works with spreadsheets as well as with tables included in other documents. If the first argument is a number, returns the nth+1 table in a document (table numbers are zero-based; if the argument is negative, the tables are counted from the end). If it's a string, the table is selected by its its logical name (as it would appear to the end user when editing the table's properties). This name is obviously easier to use than a number. Moreover, this type of selection means the application will still work even if a table changes position within a document. But the retrieval by name works with two restrictions: - if a table name is made of digits only, or if if represents a numeric expression, it's automatically regarded as a table number and the table is selected according to its sequential (zero-based) position in the document; if (and only if) the given number is greater than the position of the last table, the given argument is regarded as a name (for example, if the document contains 3 tables, getTable(365) will attempt to retrieve a table whose name is "365"); in order to avoid any retrieval by number, use getTableByName(); - getTable() can't retrieve a table by name if the name contains one or more "$", "{" or "}" characters; these characters are allowed in the table names in text documents (ODT), but not allowed in spreadsheets (ODS). The returned object is a "handle" that can be used for subsequent accesses to its components (rows, cells). The additional arguments, if any, instruct OODoc to normalize they target table in order to allow subsequent addressing of its content. If the "normalize" keyword is provided, the table will be fully normalized. If length and width arguments are provided instead, only an accordingly limited area, beginning at the "A1" position. Practically, getTable() uses normalizeSheet() in order to perform this job, so you should have a look at the normalizeSheet() documentation (in the same chapter) for explanations. Examples: my $sheet = $doc->getTable('Checklist'); returns the reference of the sheet (or table) corresponding to the given name, without processing my $first_sheet = $doc->getTable(0); my $last_sheet = $doc->getTable(-1); returns the references of the first and the last tables according to the physical order of the document ($lines, $columns) = $doc->getTable('Friends', 'normalize'); fully normalizes the table whose title is "Friends" and returns itself size. =head3 getTableByName(name [, 'normalize']) =head3 getTableByName(name [, length, width]) Retrieves a table according to its name (if it exists). This methods allows the retrieval of a table whose name is made of digits without possible confusion between names and numeric positions. The optional arguments and the limits are the same as for getTable(). =head3 getTableCell(table, row, column) =head3 getTableCell(table, coord) =head3 getTableCell(row, column) Returns the element which represents the given cell. Possible arguments are respectively: the table number or its reference in the document, row number and column number. Each table cell contained in the body of an OOo/ODF document can be referenced in this manner, as if it belonged to a single 3D table irrespective of the rest of the document. If the cell is defined in the spreadsheet but covered (because of a cell merge), the return value is undef. In other words, this method doesn't provide access to a covered cell. The first argument can be either the sequential number of the table (starting at 0), the logical name of the table, or a 'table' object (which can be retrieved in advance using getTable). If it's a number or a name, getTable() is automatically called by getTableCell() in order to convert it in a 'table' object. However, if the first argument is a row object (previously obtained via getRow() or getHeaderRow()), the second one is processed as the column number. Before using several cells in the same row, it's a good idea to get the row object and then to use it in every cell selection, in order to minimize the coordinates calculation. In tables including one or more header rows, the best way to get a header cell is to use a header row (previously obtained using getHeaderRow()) as the first argument. If the first argument is a table, getCell() looks in the table body only. Alternatively, the user can provide the cell coordinates in a single alphanumeric argument, beginning with one or two letters and ending with one or more decimal digits, according to the same logic as in a spreadsheet. So, for example $doc->getTableCell($table, 'B12'); is equivalent to $doc->getTableCell($table, 11, 1); (Remember that, with the numeric coordinates, the row number is the first argument, while with the alphanumeric, spreadsheet-like ones, the column letter(s) come first.) Numbers can also be negative, where position -1 is the last. For example: $cell = $doc->getCell(-1, -1, -1); returns the very bottom right cell of the very last table in the document $doc. Returns a null value if the given cell does not exist or if it's covered by the span of another cell. Any cellXXX() method in this module uses the same cell addressing logic as getTableCell(). CAUTION: Remember that OODoc works with the XML representation of the tables, and not with the tables themselves. The [x,y] direct addressing feature works as long as there is a continuous, one-to-one mapping between the logical view and the physical XML storage of the table. But, according to the OpenDocument specification, several contiguous objects (cells or rows) are allowed to be mapped to a single XML object when they have the same content and the same style, in order to save some storage space. This optimization is systematically used, for example, by OpenOffice.org Calc. In addition, OODoc can't address a cell that could be displayed through the GUI of a typical interactive spreadsheet software but that isn't stored because it's not initialized yet. As a consequence, the direct addressing logic of getTableCell() may require some preprocessing. See normalizeSheet() and/or expandTable() about such preprocessing. Remember that the table addressing is zero-based and the row comes before the column in OpenOffice::OODoc, so, for example: $cell1 = $doc->getTableCell($table, 0, 0); $cell2 = $doc->getTableCell($table, 31, 25); returns respectively the A1 and Z32 cells. Note: in a spreadsheet, (0,0) are the coordinates of the "A1" cell, and, for example, (16, 25) are the coordinates of the "Z17" cell. =head3 getTableColumn(table, column) See getColumn. =head3 getTableHeaderRow(table [, row_num]) Returns the element reference which corresponds to a row in a table header, or undef if the given table has no header row. The arguments are processes in the same way as with getRow(), but the second argument is optional; it's required only if the table has more than one header row (the 1st header row is returned by default). The returned elements can be used with subsequent cell access methods in order to process header cells (see getTableCell()). =head3 getTableList() Returns a list of table elements in a document. =head3 getTableRow(table, row) See getRow. =head3 getTableRows(table) Returns the list of the rows contained in the given table. When the user needs to process every row in large tables, this method allows some performance improvements, because it's less costly than a lot of successive getRow() calls. =head3 getTableSize(table) Returns the size of a table as a pair of values which represent the number of rows and columns. The table can be specified either by number, logical name or reference. Example: my ($rows, $columns) = $doc->getTableSize("Table1"); =head3 getTableText(n) Returns the content of a table, if found, whose number or reference is given as an argument. If not found, returns undef. The content of each cell is extracted according to the rules of getCellValue. In a list context, the returned value is a 2D table with each element containing the corresponding cell in the document. In a scalar context, the content is returned as a single string in CSV format. In this case, the rows are separated by a delimiter set by the instance variable 'line_separator' and the fields by the variable 'field_separator' in the OODoc::Text object. (These delimiters are by default "\n" and ";" respectively.) =head3 getText(path, position) =head3 getText(element) Exports the text contained in the given element according to the means appropriate to that type of element. If the 'use_delimiters' flag is set to 'on' (default), the content of each element (others than ordinary paragraphs, table cell, headers) is preceded and/or followed by a character string depending on the type of the element. This also depends on the settings given to the delimiter values 'begin' and 'end' by the 'delimiters' hash. In a default configuration where the application has not provided any specific delimiters, the following delimiters are used: - '<<' before and '>>' after sections of text highlighted within an element (e.g. words in bold or underlined within a paragraph of 'standard' font characters). footnote citations (in text body) are placed between square brackets. '{NOTE:' and '}' for the content of footnotes. (Footnotes are physically inserted into the text at the place where they are called, just after the link element indicating the footnote's number. Its display at the foot of the page or elsewhere is a trick of the graphical interface.) An application can change these delimiters, add more for other types of elements (e.g. paragraphs, headers, tables cells, etc.), or deactivate them using outputDelimitersOff. This depends on where the text is exported to e.g. display in editable "flat" format, conversion to non-OpenDocument XML or a markup language other than XML, generating code from text, etc.. A default export (ex: "\n") terminator can be set for any element that is not listed in the 'delimiters' hash (see defaultOutputTerminator() above). If the element is an ordered or unordered list, the text produced is a concatenation of all the lines in the list, each separated by a line-break in addition to any delimiters. The default line break character is "\n", but it can be set to any other string (including an empty string) through the 'line_separator' property of the document object. If the element is a string table cell, getText behaves like getCellValue. If the cell contains more than one paragraph, the text produced is a concatenation of all the paragraph contents, each separated in the same way as list items. If the element is a table, getText behaves like getTableText. =head3 getTextBoxElement(name/number) Retrieves a text box element by its unique name or by its order number in the document (or in the current context). =head3 getTextContent() Returns the text of a document, as "flat" editable text. In a list context, the content is returned as a table with one text element (header or paragraph) per element. In a scalar context, the content is returned as a single character string with each text unit (header or paragraph) separated by a line-feed ("\n"). The returned text contains no style or level information, so there is nothing to distinguish a header from a paragraph. Same as selectTextContent('.*'). =head3 getTextElementList([context]) Returns the list of all the text elements, including headers, paragraphs and item lists, that directly belong to an optional given context. Without context argument, the default context is the document body. =head3 getTopParagraph(n) Same as getParagraph but only considers top level paragraphs. The contents of lists, tables and footnotes are excluded. =head3 getUnorderedList(n) Returns the element which represents the nth+1 unordered list in a document, if found. WARNING: Ordered lists are possible in the OpenOffice.org 1 format only. Don't use it against OpenDocument. =head3 hyperlinkURL(hyperlink [, url]) Get/set the URL of an hyperlink element. The first argument may be a previously retrieved hyperlink element (see selectHyperlinkElement below), or the URL of an existing hyperlink. If a second argument is provided, it replaces the URL of the hyperlink element. With only one argument, just returns the existing URL of the link, or undef if the first argument doesn't match an existing hyperlink element. =head3 inputTextConversion(text) Returns the UTF8 conversion of the given text, supposed to be in the local character set of the document (see the 'local_encoding' property). =head3 insertColumn(table, col_num [, options]) Inserts a new column in an existing table at a given position. The second argument must be the number of an existing column. Caution: this argument must be a column number, and not a column element. The new column is created as a copy of the column a the given position. It's inserted before or after the existing one, according to an optional "position" parameter (default 'before'). Caution: before using insertColumn() against a spreadsheet, the application should ensure that the whole rectangular area from the top left cell ("A1") to the last used cell of the column at the target position is "normalized" (see normalizeSheet() for details about the table normalization). =head3 insertDrawPage(page/pos [, options]) In a presentation or drawing document, inserts a new page before or after an existing page. Possible options are the same as for appendDrawPage(), with an additional one: position => 'before' or 'after' (default 'before') The new page is inserted before or after the reference page, according to the 'position' option. The first argument can be a draw page element reference (recommended) previously returned, for example, by a previous page retrieval or creation method call. Alternatively, it can be a page position or visible name, so it's regarded in the same way as in getDrawPage(). Returns the new page element, or undef in case of failure. =head3 insertHeading(path, position, options) =head3 insertHeading(element, options) Same as appendHeading, but inserts the new heading before or after another element. Position is that of an existing element which can be another heading or a paragraph. Can be given by [path, position] or by element reference. Possible options are the same as for appendHeading, with the additional option 'position' which determines if the heading is inserted before or after the element at the given position. Possible values for this option are 'before' and 'after'. By default, the new element is inserted before the given element. =head3 insertItemList(path, position [, options]) =head3 insertItemList(element [, options]) Same as appendItemList, but a new list is inserted at the given position. The point of insertion can be given either by the pair [path, position] or by element reference. Options are the same as for insertParagraph. =head3 insertParagraph(path, position [, options]) =head3 insertParagraph(element [, options]) Same as appendParagraph, but a new paragraph is inserted at the given position. Position is that of an existing element which can be another paragraph or a header. Can be given by [path, position] or by element reference. Options are the same as for appendParagraph, with the additional option 'position' which determines whether the paragraph is inserted before or after the element at the given position. Possible values for this options are 'before' and 'after'. By default, the element is inserted before the given element. =head3 insertRow(table, row [, options]) =head3 insertRow(row_element [, options]) Inserts a new row into a table. In its first form, pass the table (reference, logical name or number) and the position number in the table. In its second form, pass the element reference of the existing row which is directly before or after the position where you want to make the insertion. By default, the new row is inserted at the position of the referenced row, which displaces it and the rest of the table down by one row position. However, you can insert it after by using the 'position => after' option. By default, the new row is an exact copy of the referenced row, but you can assign particular attributes to it in the same manner as the insertElement method of OODoc::XPath. =head3 insertSection(path, position, name [, options]) =head3 insertSection(element, name [, options]) Creates a new section and inserts it immediately before or after an existing element (paragraph, header, table). The referenced element can be indicated as in insertParagraph. There is a "position" option which works in the same way as with insertParagraph() or insertRow(). For other options, see appendSection(). For example, insertSection() may be used in order to insert a subdocument in a master document. =head3 insertString(path, position, text, offset) =head3 insertString(element, text, offset) Inserts a flat character string in a given element (whatever the type of element) at the given offset. If the offset is not defined, the text is appended to the end of the element (however, if the offset is provided and set to zero, the string is inserted at the beginning). =head3 insertTable(path, position, name, rows, columns [, options]) =head3 insertTable(element, name, rows, columns [, options]) Creates a new table and inserts it immediately before or after another element (paragraph, header, table). The referenced element can be indicated as in insertParagraph. The other arguments and options are the same as for appendTable with the additional option 'position' as in insertParagraph. =head3 insertTableColumn(table, col_num [, options]) See insertColumn(). =head3 insertTableRow(table, row [, options]) =head3 insertTableRow(row_element [, options]) See insertRow(). =head3 lockSection(section [, key]) Installs a write protection on the given section. If a second argument is provided, it's stored as an encrypted key which is associated to the write protection. Caution, it's not the key as it should be typed by the OOo end-user. Such a write protection works only when the document is edited through an OpenOffice.org-compatible desktop software. It doesn't prevent the programs using OpenOffice::OODoc from deleting or updating the protected sections. =head3 makeHeading([options]) Creates a new heading element, or marks as a heading an existing element. Options: element => an arbitrary existing element If this option is provided, the given element is converted in place to a heading, whatever its original type and position. No element is created. Without the 'element' option, a new heading element is created and returned for later use. This element is free; it's not automatically attached somewhere in the document. For direct heading creation and attachment, you should prefer appendHeading() or insertHeading(). level => a numeric, positive integer value Sets the hierarchical level of the heading (remember 1 is the top heading level). Caution: no default value. style => the name of a convenient heading style While it's not mandatory, the 'style' option and a properly defined heading style are generally required in order to allow the office software to really process and display the element as a heading with the right hierarchical level. Of course, any previously existing hierarchical style is reusable here. The main purpose of this method is to allow quick heading hierarchy creation in a "flat" document. For exemple, an application can select a set of flat paragraphs matching a given condition and convert each one in place to a heading with a given level. =head3 moveElementsToSection(section, list) Moves a list of elements from any place to a section. The section may be passed by name or by element reference; it must be an existing one (no new section is created). The list is a set of arbitrary elements (including sections). Each one is cut from its previous place and appended to the section in the order of the list, without document consistency check. =head3 normalizeSheet(sheet, rows, columns) =head3 normalizeSheet(sheet, 'full') This method preprocesses a given sheet so its components (rows, cells) become available for all the table-oriented methods described in this chapter. In some situations, this method must be used before any attempt to address any individual table component (column, row or cell). The return value is the target table object in a scalar context and the size (height, width) in an array context. This method works with any kind of ODF tables, whatever the containing document, and not only with spreadsheets. In the first form, the 2nd and 3rd arguments define the size of a rectangular area, beginning at the first cell ([0, 0] or "A1"), to be processed, in order to save time and CPU resources when the application needs to address objects only in the first corner of a huge table. The second form allows the OODoc to normalize the whole table, whatever the size. It's certainly the preferred form, as long as the target sheets are reasonably sized or the hardware is powerful enough. The processed area becomes a workspace which is safely addressable by any cell/row/column processing method. This preprocessing is sometimes required, sometimes not. For example, it's required on present OpenOffice.org Calc spreadsheets, and useless on present OpenOffice.org Text tables. It's automatically executed when getTable() is called with size arguments (or with the "normalize" option); therefore it's not always explicitly invoked by the applications. However, it's useful to know its purpose. The object addressing logic (which, for example, allows a program to directly reach a cell using its coordinates) relies on a continuous, regular mapping between the user's view and the physical XML storage of the tables. However, the OpenDocument specification allows any conforming application to map more than one table element to a single XML element. When two or more contiguous objects contain the same value and have the same style and the same data type, they *may* be mapped to a single XML element with a repetition attribute. As a consequence, the position of the appropriate XML element can't be directly calculated from the logical coordinates of the object, and OODoc needs to scan the table in order to get all the repetition attributes and calculate the real mapping. In addition, updating an object whose the XML corresponding element has a repetition attribute would automatically update all the objects mapped to the same element, producing unpredictable and generally wrong results. OpenOffice.org Calc systematically uses this storage optimization in spreadsheets, while OpenOffice.org Writer doesn't use it for tables in text documents. In Calc (sxc/ods) documents, the XML mapping of the whole content is "denormalized" in order to save memory: several table components can be mapped to a single XML element, so the XML address of each one can't be simply calculated from its logical coordinates. In order to allow the spreadsheet components to be addressed with the same methods as the Writer table components, normalizeSheet() reorganizes the XML mapping of the given sheet. Caution: The OpenDocument specification doesn't make any difference about this point between tables included in text documents and tables in spreadsheet-only documents. So any ODF-compliant application *could* denormalize the XML storage of any table and use the repetition attributes. As a consequence, normalizeSheet() *could* be required in the future for other documents than OOo Calc ones. This method is not (presently) always needed for tables included in OpenOffice.org Writer (odt/sxw) documents, because their storage is "normalized" (i.e. each component is mapped to an exclusive XML element), with the exception of the column objects. So, normalizeSheet() is required with these tables when the application needs to use a column-focused method such as getColumn(), insertColumn() or deleteColumn(). In the other hand, normalizeSheet() is not required to address a sheet which has been created through the OODoc methods (provided that the document has not been edited with another application software in the meantime). These methods, i.e. appendTable() and insertTable(), create normalized tables, whatever the document class. Because this method is time and memory consuming, it should never be used to reorganize the largest possible area of a sheet (meaning thousands of rows and hundreds of columns that will probably never be used). So it's action is limited to a given area, controlled by the rows, columns arguments. When these arguments are not provided, the method uses the 'max_rows' and 'max_cols' properties instead (see the Properties section for other explanations). The processed area should be sized in order to cover all the cells to be reached by the program, and nothing more. The first argument can be either the logical name of the sheet (as it's shown in the bottom tab by OOo Calc), the sheet number, or a table object reference, previously returned by getTable(). The return value is the table object (or undef in case of failure). Example: $doc = odfDocument(file => 'report.ods'); my $sheet = $doc->normalizeSheet('Sheet1', 7, 9); my $result = $doc->cellValue($sheet, 5, 6); In the sequence above, a top left area of 7 rows by 8 columns is pre-processed, so the cells from A1 to H6 of this sheet can be reached according to the same addressing scheme as in Writer tables. The last instruction gets the content of G6. Note that the second line of this example could be replaced by my sheet = $doc->getTable('Sheet1', 7, 9); knowing that, when called with size arguments, getTable() automatically executes normalizeSheet(). The following code normalizes the whole table, whatever its size (but I don't recommend this option for tables containing thousands of rows by hundred of columns): $doc->normalizeSheet('Sheet1', 'full'); This last instruction could be automatically and silently executed through the following one: $doc->getTable('Sheet1', 'normalize'); The transformed sheets, of course, are readable by OOo Calc. They simply take some more disk space when the processed spreadsheet is saved. If the document is later read then written by OOo Calc, the storage is optimized again, so the effects of normalizeSheet() disappear. normalizeSheet() is neutral against already normalized tables. An explicit call to this method can be replaced by getTable() with the additional length and width parameters. In addition, normalizeSheet() is automatically executed before resizing each time a table is processed by expandTable(). =head3 normalizeTable(table [, rows [, columns]]) See normalizeSheet(). =head3 outputDelimitersOn() =head3 outputDelimitersOff() Turns delimiters on or off. Used to mark up text exported by certain methods like getText or selectTextContent. The delimiters actually used depends on the table loaded into the OODoc::Text instance via the 'delimiters' property. =head3 outputTextConversion(text) Returns the conversion in local character set of the given text, supposed to be in UTF8. The local character set of the document is used (see the 'local_encoding' property). =head3 removeBookmark(id) See deleteBookmark(). =head3 removeHeading(position [, level => level_no]) =head3 removeHeading(element) Removes the given heading element. Example: $doc->removeHeading(4); removes the 5th heading (whatever its level) counted from the beginning of the document. See getHeading() for the argument and option. If the argument is an element reference (second form), the type is not checked and this method becomes the equivalent of removeElement() (which is documented with OpenOffice::OODoc::XPath generic methods). =head3 removeHyperlink(path, position) =head3 removeHyperlink(element) Removes any hyperlink contained in the given element, leaving in place the previously hyperlinked text. =head3 removeParagraph(position) =head3 removeParagraph(element) Removes the paragraph at the given position (first form). The paragraph to be removed can be indicated by element reference (second form). In this case, the type of element is not checked and this method becomes the equivalent of removeElement. =head3 removeCellSpan($cell) Removes the multi-column, multi-row span of a table cell. The width and height of the cell are reduced to one column and one row.The uncovered cells take the same style and data type as the reduced cell. Caution: This method works with cells that heve been expanded using the "number-rows-spanned" and "number-columns-spanned" OpenDocument attributes. The cell expansion is done this way by the cellSpan() method, as well as with the present version of OpenOffice.org Calc. But other applications (including the present version of OpenOffice.org Writer) can implement the cell merge using subtables instead of span attributes. =head3 removeSpan() See removeTextStyleChanges(). =head3 removeTextStyleChanges(path, position) =head3 removeTextStyleChanges(element) Removes all the text style that apply to particular text spans in the paragraph or a heading provided as argument, so all the content will be displayed according to the text style that is defined at the paragraph level (font, colors, etc). Works with paragraphs or headings only. For a more drastic result, see flatten() in OpenOffice::OODoc::XPath. See also setTextSpan(). Caution: there is no symmetry between setTextSpan() and removeTextStyleChanges(). The first one creates one styled text span at a time in a given context that may be larger than a single paragraph. The second one removes all the styled text spans in the given context, but the context is a paragraph or a heading only. =head3 renameSection(section, newname) Renames an existing section using the second argument. =head3 renameTable(table, newname) Renames an existing table using the second argument. =head3 replaceText(path, position, filter, replacement) =head3 replaceText(element, filter, replacement) Replaces all sub-strings which match "filter" with "replacement" in the text of an element (and its descendants) indicated by [path, position] or by reference and returns the modified text. The "filter" string can be an "exact" literal or a regular expression. Example: $doc->replaceText($p, "C(LIENT|USTOMER)", $contact); replaces each occurrence of "CLIENT" and "CUSTOMER" with the content of the $contact variable in the paragraph $p of document $doc. The "replacement" argument can be a function reference. In which case, the function is called each time the string is matched, and the value returned by the function is used as the replacement value. sub action { my $arg = shift; my $text = shift; print "$arg : $text\n"; return "OK"; } $doc->replaceText($p, $expression, \&action, "Found"); displays "Found: " (where is the text retrieved) each time a string matches $expression and replaces this string with "OK". If $expression contains an "exact" string (not a regexp), then clearly the text displayed will always be the same string. However, if it happens to be a regular expression, it is in effect the text retrieved which will be displayed. Generally speaking, if the replacement value is a function reference, the called function receives the remainder of the arguments which follow it, in this order: 1) all the arguments following the function reference in the replaceText() call, in the same order; 2) the string that matches the filter argument. See also substituteText(), which should be preferred in most situations. =head3 rowStyle(row_element [, style]) =head3 rowStyle(table, row [, style]) Reads or modifies a table row's style, in the same way as columnStyle does for columns. =head3 sectionProtectionKey(section) Returns the encrypted key which is associated to the given section, if the section is write-protected by key. This method can't provide the real key (as it should be typed by the end-user to unlock the section), but the returned value may be reused in order to protect more than one section with the same password. See also unlockSection(). =head3 sectionStyle(section, [newstylename]) Without argument, returns the current style of a given section. If an argument is provided, it becomes the new style of the section. =head3 selectDrawPageByName(name) In a presentation or drawing document, returns the page element identified by the given name, or undef if the name is unknown. The names to be used correspond to the displayed page names in OpenOffice.org Impress. =head3 selectElementByBookmark(name) Returns the element containing the given bookmark. Caution: this method works with position bookmarks only, not with range bookmarks (a range bookmark can be spread over several text elements). =head3 selectElementByContent([context,] filter [...]) Returns the first text element whose content matches the 'filter' (which can be an exact string or a regular expression), or undef if no matching content is found. With additional arguments after the filter, this method can be used for replacement operations, or user-defined function triggering, in the same conditions as selectElementsByContent(). The retrieval functionality of selectElementByContent() is the same as selectElementsByContent(). See selectElementsByContent() for limits. =head3 selectElementsByContent([context,] filter) =head3 selectElementsByContent([context,] filter, replacement) =head3 selectElementsByContent([context,] filter, action [, other_arguments]) This method returns the text elements whose content matches the search criteria contained in 'filter' (a string that may be a regexp). Note that this method can be used with a "non-filtering" regular expression (".*") for unconditional movement through all text elements. The default scope is the current context (practically it's the whole document unless it has been changed using the currentContext() method). However a context element may provided before the filter argument in order to restrict the search space. Be careful: if the search is successful, the returned elements may be of various kinds; they are not always paragraphs or headings. They may be, for example lower level text elements contained in paragraphs, such as text spans or text hyperlinks. The first form simply returns the given list without modifying the text. The second form returns the same list, but replaces all strings which match the search criteria with the 'replacement' string as it goes. The third form, where the 'action' argument is a program function reference, launches the given function each time the filter string is matched. If defined, the value returned by the function is used as the replacement value. If the function returns a null value (undef) then no replacement is made. If it returns an empty string, the retrieved text is deleted. The called function receives the rest of the arguments, in this order: 1) all remaining arguments after 'action' ('other_arguments'), if any. 2) the element containing the retrieved text. 3) the string actually selected. If the filter is an exact string, it is equal to the filter. If the filter is a regular expression, it matches the "real" text retrieved. The returned text (if any) must be encoded in UTF8. The returned list is the same one returned by the first two forms. Example: sub action { my ($d, $element, $value) = @_; if ($value < 100) { $d->removeElement($element); return undef; } else { return $value * 2; } } @list = $doc->selectElementsByContent("[0-9]+", \&action, $doc); In the above code, the subroutine "action" is called each time an integer (one or more digits) is found. The subroutine receives the document reference itself as its first argument (an OODoc::Text object given by the application). Next, it automatically receives the reference of the element in which the search string was found (i.e. an integer) and, finally, it receives the exact number found as its second-last and last arguments respectively. If this number is less than 100, the element is removed. This is why the subroutine needed the $doc object, used to invoke the removeElement method. If more than 100, the number is multiplied by two and the result replaces the original value in the element. The list returned by selectElementsByContent contains all elements which contain the search string, including any which might have been removed by the called function while it was running. It is the "main" elements containing strings which matched the filter which are returned and not any of their sub-elements. For example, if the returned string is found in one of the items in an unordered list, the list element is selected and not the item. Similarly, the table is selected when one of its cells matches the filter, and the paragraph which is selected when the search string is found in an attached footnote. Important limit: This method can't retrieve elements whose display content apparently matches the given filter but whose internal storage doesn't. For example, a paragraph containing "foo bar" will never be selected through selectElementByContent() if "foo" and "bar" have different text styles. In the same way, a substring containing multiple successive whitespaces will never match, because, according to the ODF standard, multiple spaces (like tabs or line breaks) are stored as special XML element instead of flat text. A character string cannot be considered to match the filter unless it is entirely within the same sub-element and all its characters have the same style. More generally, a substring can match the filter if and only if it's represented with only one style and if it doesn't contain multiple spaces, tab stops or line breaks. =head3 selectHyperlinkElement(url_filter) Retrieves the first hyperlink element (if any) whose the URL matches the argument. Example: my $e = $doc->selectHyperlinkElement("cpan"); could return an hyperlink element containing "www.cpan.org" as well as "search.cpan.org", etc. The URL filter is processed as a regexp. Note: In order to get the text container (ex: paragraph) where the hyperlink is located, the application can use the parent() element method. Example: my $e = $doc->selectHyperlinkElement("www.cpan.org"); my $p = $e->parent if $e; =head3 selectHyperlinkElements(url_filter) Returns the list of the hyperlink elements whose the URL matches the argument (and not only the first one). =head3 selectParagraphByStyle(stylename) Returns the first paragraph (if any) using the given style. =head3 selectParagraphsByStyle(stylename) Returns the list of the paragraphs using the given style. =head3 selectTextContent(filter) =head3 selectTextContent(filter, replacement) =head3 selectTextContent(filter, action [, other_arguments]) Returns a list of header texts and/or paragraphs (in the document's own order) which match the given search criteria. The filter can be an exact string or a regular expression. A filter set to ".*" (no selection) will result in an export of the entire text. In all three forms, this method behaves like selectElementsByContent, except that it returns text instead of a list of elements. Depending on the context (list or scalar), the result is returned in the form of a list of rows or in the form of a single character string where the elements are separated by a line-feed ("\n"). Note: called with a "non-filtering" regular expression, this method will result in a "flat" export of the document: print $doc->selectTextContent('.*'); =head3 setAnnotation(element [, options]) Creates and inserts an annotation in a given element. The possible options are: 'date' => the date/time of the annotation (ISO-8601 format); the default is the current system date/time 'author' => the name of the author of the annotation; unless this option is provided; the default is the current system user name 'text' => the text content of the annotation (no default) 'style' => a paragraph style for the annotation (no default) 'offset' => an integer value that specifies the position of the insertion point of the annotation (default=0); a negative value means that the position is counted backward from the end 'before' => a regular expression; specifies that the insertion point should be before the first match 'after' => same as 'before' but the insertion point should be after the first match 'replace' => same as 'before', but the matching substring is deleted and replaced by the annotation If 'before', 'after' or 'replace' (which are mutually exclusive) is set, the 'offset' option, if provided too, specifies a search space restriction and a search way. If 'offset' is positive, the search space runs from 'offset' to the end; if 'offset' is negative, the search space runs from the end and its size is the absolute value of 'offset'. If 'offset' is 0, then it's possible to force the search backward from the end to the beginning using an additional 'way' parameter whose possible values are 'backward' and 'forward' (if 'way' is 'backward', then 'offset' is regarded as negative whatever its sign). Note that the 'capture' option doesn't work with annotations. Returns the reference of the annotation element. Note that this reference could be used later as a context for additional insertions (for example in order to append several paragraphs to the content of the annotation). =head3 setBibliographyMark(element, [, position_options][attributes]) Creates a new bibliography mark within a given text element. The content of the new bibliography entry may be initialized through a 'attributes' parameter whose value is a hash. All the possible attributes of an ODF-compliant bibliography entry, such as author, editor, isbn, title, year, and many others, are allowed. A 'identifier' parameter is mandatory in order to get a visible mark; note that this identifier is bibliography-specific and is not the same as the generic identifier that could be get or set using getIdentifier() and setIdentifier(). Other attributes are optional (but, of course, an entry with nothing more than an identifier would not be very useful in a final document). By default, the object is inserted at the beginning of the target text element. But, thanks to the optional position parameters, it can be put anywhere within the text of the bookmarked element. The position parameters are 'offset', 'before', 'after', 'replace', and 'way' and they work like with setAnnotation(). $para = $doc->selectElementByContent("ODF-related book"); $doc->setBibliographyMark ( $para, offset => -20, replace => "reference needed", attributes => { identifier => "JDE", title => "OASIS OpenDocument Essentials", author => "J. David Eisenberg", year => 2005, isbn => "1-4116-6832-4" } ); This sequence replaces a "reference needed" substring in a given paragraph by a bibliography mark that will be displayed by default as "[JDE" and that contains various attributes. The 'replace' option means that the given substring will be deleted and replaced by the mark. The given (negative) offset means that the substring must be searched backward and that the last 20 characters of the paragraph must be excluded from the search space. =head3 setBookmark(element, name [, position_options]) According to the structure of the optional parameters, this method may be used either to set a position bookmark (i.e. a named place holder at some point of the text content in a pararaph) or to set a range bookmark (i.e. a range of text that may spread across paragraph boundaries and that is delimited by a bookmark start and a bookmark end elements). The first form is illustrated by the following example: $doc->setBookmark( $paragraph, "BM001", offset => -20 before => "xyz" ); This sequence puts a bookmark identified by "BM001" in a given paragraph, immediately before the first "xyz" substring found in a backward search among the last 20 characters. The mandatory arguments are the target text element and the name of the new bookmark (that should be unique). By default, the object is inserted at the beginning of the text. But, thanks to the optional position parameters, it can be put anywhere within the text of the bookmarked element. The position parameters are 'offset', 'before', 'after', 'replace', and 'way', and they work like with setAnnotation(). See also setChildElement() in OODoc::XPath. However, the 'text' option is ignored, knowing that a bookmark has no text content. If the 'replace' option is used, the matching substring is deleted and replaced by the bookmark, but the deleted text is not reused. The second form requires a bookmark name as the 1st argument, then a 'start' and a 'end' parameters are required; each one is a hash of parameters that specifies the position of one mark according to the same options as for a position bookmark (namely with 'offset', 'before', 'after', 'replace', and 'way'). In addition, each of the 'start' and 'end hashes may contain an additional 'context' parameter that specifies the elements that will contain the start and end marks, respectively. The following example creates a range bookmark that starts after the 15th character of $p1 and ends before the "xyz" substring in $p2, assuming that $p1 and $p2 are previously selected paragraphs in the right order: $doc->setBookmark( "BM002", start => { context => $p1, offset => 15 }, end => { context => $p2, before => "xyz" } ); The user is not prevented by default from creating a range bookmark whose start point is after the end point of a range bookmark. However, it's possible to force an order check using a boolean 'check' option. If 'check' is 'true' while the order is wrong, the bookmark is not created. Note that the second form of setBookmark() is the same as setRangeBookmark(). =head3 setHyperlink(path, position, [context,] expression, url) =head3 setHyperlink(element, expression, url [, options]) Puts an hyperlink on a text area in a given text element. The first substring matching the given "expression" argument in the text element (if any) will become the hyperlinked text. The "url" argument is, of course, the URL of the hyperlink. If successful, the method returns the new hyperlink element, or undef otherwise. This short example illustrates the simplest use: $doc->setHyperlink($para, "CPAN", "http://www.cpan.org"); This method works in the same way as setTextSpan(), described below, but the text span is hyperlinked, and not only presented according to a particular style. So, the third argument must be an URL instead of a style. A set of hyperlink attributes may be optionally provided as a hash through an optional 'attributes' parameter. For example, the application can provide a 'style-name' and a 'visited-style-name' as shown below: $doc->setHyperlink ( $para, "CPAN", "http://www.cpan.org", attributes => { 'style-name' => "ToBeVisited", 'visited-style-name' => "Visited" } ); 'style-name' selects the style which applies to the text of the hyperlink, as long as the URL is not visited, while 'visited-style-name' indicates, of course, the style in use if the link location was already visited. These styles must belong to the 'text' family. Other allowed hyperlink attributes are listed in the 5.1.4 of the OASIS OpenDocument specification. They may be set through the 'attributes' options or later through the common setAttributes() method (that may apply to the object returned by setHyperlink). Note: The hyperlink is not always a remote URL, such as in the example above. Internal references ere allowed as well. An internal reference is prefixed by "#". If an internal reference is a heading, it's prefixed by "#" and suffixed by "|outline". An hyperlink may be aimed at a location inside another document; such a link is the concatenation of a file path, a "#", and a local name that makes sense in the target document (bookmark, heading...). =head3 setIndexMark(element, id [, options][, start =>{}, end =>{}]) Creates an alphabetical index entry in a given element. The first arguments are the target element (generally a paragraph) and a mandatory identifier (that should be unique). A 'type' parameter allows to select the type of index; possible values are 'alphabetical-index' or 'toc' (the last one stands for "table of contents index mark"). The default is 'alphabetical-index' (it may be wrote 'alphabetical index', knowing that every space is automatically interpreted as a '-'). A 'content' parameter allows to specify an expression; the first substring in the element that matches this expression will become the indexed substring. It's possible to restrict the search area using a 'offset' option, a positive or negative integer; a positive value means that the search space runs from the beginning to the given offset; a negative value means that it runs backward from the end to the given offset. In addition, a 'way' option whose legal values are 'forward' or 'backward' may control the search way: if 'way' is 'backward' then the search is done backward even if 'offset' is not provided, and 'offset' is regarded as negative even if it's provided without the minus sign. Unless 'offset' is defined and negative, the default way is 'forward'. The code below puts an index entry, identified by "idx001" and related to the first match of a "xyz" expression: setIndexMark( $paragraph, "idx001", type => 'alphabetical index', content => "xyz" ); Note that the 'type' option is provided for clarity only, knowing that 'alphabetical-index' is the default. The following variant puts a TOC mark, searched backward from the end of a given paragraph, within a 20-character-long area: setIndexMark( $paragraph, "idx001", type => 'toc', content => "xyz", offset => 20, way => 'backward' ); The same result in this second example could be obtained without the 'way' option, with -20 instead of 20 as the 'offset' value. A more sophisticated set or parameters may be provided in order to specify the beginning and the end of the index mark separately. To do so, 'content' must be omitted and replaced by the 'start' and 'end' parameters, each one being a hash, just like with the second form of setBookmark() that created a range bookmark, with a restriction: The 'start' and 'end' optional parameters allows to specify the start and the end positions of the index mark. Each one is a hash that may contain the same options as with setBookmark() described below, namely 'offset', 'before', 'after', 'replace', and 'way', with the exception of 'context', because (unlike a bookmark) an index mark is entirely included in a single paragraph; its start and its end belong to the same context, that is specified by the first argument. The following example creates an alphabetical index mark that is associated to a text range running from the "xyz" substring to the end of the given paragraph: $doc->setIndexMark( $paragraph, "idx002", type => 'alphabetical index', start => { before => "xyz" }, end => { offset => 'end' } ); =head3 setNote(path, position, text [, options]) =head3 setNote(element [, options]) Creates and inserts a footnote or an endnote in the given element with the given text as the note content. Returns the new note element in case of success, or undef if the target element doesn't exist. Supported options are: 'text' => the text content of the note 'class' => specifies the display class of the now note; may be 'footnote' or 'endnote', default is 'footnote'; 'id' => a note identifier, must be provided by the application and must be unique for the class (be careful, the uniqueness is not automatically checked and no default is provided); 'citation' => specifies the character string to display as note citation (at the place in the host element where the note is anchored); 'label' => this option, if provided, means that the note should not be processed as automatically numbered by the printing/editing applications and that it should be represented by the given (arbitrary) string; if 'label' is defined, it becomes the default value of 'citation'; 'style' => specifies the style of the note content (should be a regular paragraph style). By default, the object is inserted at the beginning of the text. But, thanks to the optional position parameters, it can be put anywhere within the text of the bookmarked element. The position parameters are 'offset', 'before', 'after', 'replace', and 'way' and they work like with setAnnotation(). =head3 setRangeBookmark(name, start => {options}, end => {options}, ...) Like the second form of setBookmark(): creates a range bookmark according to a mandatory name (1st argument) and position options provided through 'start' and 'end' parameters, each one being a hash of options. See setBookmark() above for details. =head3 setSpan() Deprecated. See textStyle(), setTextSpan() and setTextSpans(), more powerful but using different options. =head3 setStyle(path, position, style_name) =head3 setStyle(element, style_name) Obsolete. See textStyle(). =head3 setText(element, text ,[text, ...]) Alters the setText method of OODoc::XPath, so that it can handle complex text elements. Please read the setText() entry in OpenOffice::OODoc::XPath before the present entry. If the element is a paragraph, a header or a list item (ordered or unordered), its content is replaced by the 'text' argument. Caution: setText() deletes and replaces the previous content of the paragraph. If the element is a table cell, this method is the same as updateCell. If the element is a list (ordered or unordered), the content of each 'text' argument (however many) forces the creation of a new item which is appended to the list (existing items remain unchanged). Example: $doc->setText($element, "Peter", "Paul", "John") adds three items to the list if $element is a list. If $element is, for example, a paragraph, then the second argument ("Peter") becomes the content of the paragraph and the other arguments are ignored. If the element is a note element or a note body, the given text becomes the content of the note body. If the element is a section, the whole content of the section is deleted and replaced by a single paragraph containing the given text. For all other types of $element, setText() behaves normally as defined in OODoc::XPath. Note: setText(), as any other text input method, doesn't properly process repeated spaces by default. So, a sequence of spaces, whatever its length, is replaced by a single space. See setText() and extendText() in OpenOffice::OODoc::XPath. =head3 setTextBoxContent(text_box, content) Fills the given text box according to the given content. The first argument may be the unique name, the order number or the reference of a text box. The content is processed in the same way as the content option in createTextBox(). =head3 setTextField(element, field-type [, options]) Puts a variable text field in a given text element. The 2nd argument specifies the type of field. The offical types are described in 6.2 and 6.4 in the OpenDocument 1.1 specification, corresponding to the so-called "document fields" and "metadata fields". Examples of possible field type arguments are 'page number', 'chapter', 'paragraph count', 'time', 'file name', 'creator', 'creation date', etc. The full syntax of the ODF field tags is not mandatory; the right namespace prefix is automatically added if the given type indicator doesn't contain a semicolon, and every space is replaced by a '-'. So, a field type like, say "text:word-count" may be specified as "word count". This method may be used in order to display a declared user field. To do so, the field type must be 'variable' instead of a regular ODF text field, and a 'name' parameter must be provided with the name of an existing (ot to be created) user field declaration. While the content of a text field is often computed and displayed dynamically by ODF-compliant viewers, it's possible to provide an alternative text that will be persistently stored in the field and available for applications which are note able to compute the content and/or for users who need to know the last displayed value. Such an alternative content is provided through a 'text' option. It's possible to provide the new text field with one or more attributes as a hash through a 'attributes' parameter. The most common attributes are 'fixed' and 'style'; the first one is a boolean, the second one is the name of a display format. The 'fixed' attribute, if 'true', prevents the ODF-compliant editors and viewers from refreshing the content of the field (for example a fixed date field displays the same date forever instead of the current date). The 'style' attribute is the name of a display format (it's recommended to associate every date, time or numeric field to a display format, while the default display format of some ODF editors may be convenient for some needs). Here the 'style' attribute is a shorcut for 'style:data-style-name'; see the ODF 1.1 specification 6.7.7 for a full description of this kind of styles. Other attributes depend on the kind of content. The following example creates a fixed time field; the time value is stored as standard (ISO 8601) date format and the alternative text is an arbitrary local representation of the same; the presentation style (for applications that can deal with ODF number styles) is "MyTimeStyle" (that is supposed to be the name of a time style defined as an automatic style in the document): $doc->setTextField ( $paragraph, 'time', text => "17:03:25", attributes => { fixed => 'true', 'time value' => "2010-02-25T17:03:25", style => "MyTimeStyle" ); Note that in this example, if 'fixed' was 'false' or undef, a fully functional ODF editor could dynamically update the 'time value' according to the current date and the text content according to the current time and the given style. Here the internal value is a 'time value'; it would be the same for any field type containing a time value (such as, say, a 'print time' or a 'creation time' field). For other field types, the corresponding attribute would be 'date value', 'string value', 'boolean value', or just 'value'. For any detailed information about the possible attribute combinations according to the field types, have a look at the chapter 6 of ODF 1.1. By default, the field is inserted at the beginning of the given text element. But, thanks to the optional position parameters, it can be put anywhere within the text of the element. The position parameters are 'offset', 'before', 'after', 'replace', and 'way', and they work like with setBookmark() and other methods. See also setChildElement() in OODoc::XPath. If the 'replace' option is used, the matching substring is deleted and replaced by the text field. The next example inserts the name of the author of the last change (i.e. the 'creator', according to the ODF vocabulary) as a replacement of a "AUTHOR HERE" substring searched backward somewhere in a 50-character-long area at the end of the target element: $doc->setTextField ( $paragraph, 'creator', replace => "AUTHOR HERE", offset => 50, way => 'backward' ); and the code below appends after the last character of the paragraph a 'file name' field that will display the full path of the file: $doc->setTextField ( $paragraph, 'file name', offset => 'end', attributes => { display => 'full' } ); The last example creates a display area in a given paragraph after a given substring for a declared variable whose name is "Amount" (see setUserFieldDeclaration() in OpenOffice::OODoc::XPath to see how such a variable may be declared): $doc->setTextField ( $paragraph, 'variable', name => "Amount", replace => "AMOUNT HERE" ); =head3 setTextFields(element, expression, field-type [, options]) Replaces every substring that matches the given expression in the given text element by a variable text field. See textField() in the present manual chapter for some information about text fields. This method works the same way as setTextSpans() to retrieve the strings to be replaced. However, each matching string becomes invisible and is replaced by the variable field. Optional field attributes are allowed after the field type in the same conditions as for textField(). The following example replaces every occurrence of "TIMESTAMP" in a given section by a variable field displaying a time which is 2 hours later than the current time: $section = $doc->getSection("Variables"); $doc->setTextFields ( $section, "TIMESTAMP", 'time', 'time-adjust' => 'PT02H' ); This method returns the text field elements as a list. See also setTextField(). =head3 setTextSpan(path, position, style [, options]) =head3 setTextSpan(element, style [, options]) Inserts a substring with a special text style in a selected position within the content of an existing text element (namely a paragraph or a heading). Unlike setTextSpans() and the 3-argument use of textStyle(), inserts only one span (or nothing if the conditions are not met). A "text span" is a substring whose presentation style differs from the style of the text element to which it belongs. For example, a given "span" could be in italics while the rest of the paragraph is in normal characters. A text span is a special element that contains text but that must be included in a paragraph or a heading. Caution: the same word has a different meaning when it's used about table cells (see cellSpan()). The properties of a text span can be related to any kind of character string presentation, such as font, font size, font weight, font style, and colors (background and foreground). Whatever these properties, they apply through a style. setTextSpan() works on any kind of text container, whatever its hierarchical level. For example, if the given element is a table, the span style attribution applies to every cell of the table. And the same change can be done in all the displayable content not including page headers, page footers, and page backgrounds through a single setTextSpans() call, if the given element is the document body itself (see getBody() in OpenOffice::OODoc::XPath). The first argument is the target element, the second one is the name of a text style (existing of to be defined). The optional parameters that follow specify how and where the text span should be inserted. The method returns the new text span object, or undef in case of failure. The location of the text span may be specified using the same options as the setChildElement() which is described in the OODoc::XPath manual. A 'capture' parameter, whose value is a string, means that the first substring that matches it should become the content of the new text span. The following instruction replaces the first appearance of the "ODF" substring in a given paragraph by a text span whose content will be "ODF" and whose style will be "Standout"; in other words, it tells that the style "Standout" will apply to the first "ODF" substring: $doc->setTextSpan($paragraph, 'Standout', capture => "ODF"); It's possible to apply a text style and to change the text content in a single operation using both the 'replace' and the 'text' options. If 'text' is set, its value is used as the text of the new span element. In the next example, the "ODF" substring will be removed and replaced by a text span whose style will be "Standout" and content will be "OpenDocument" instead of "ODF": $doc->setTextSpan( $paragraph, "Standout", replace => "ODF", text => "OpenDocument" ); Note that there is no default text, so if 'replace' is set while 'text' is not set, the matching substring is deleted and replaced by a text span with a style but without content, resulting in useless markup (there are more convenient ways to just delete a substring). Practically, if both 'text' and 'capture' are set, the result is the same as with 'replace'; however, as soon as the aim is to replace a substring by a text span and not to capture the content of the substring in the text span, I encourage the use of 'replace' in order to get a more self-documented code. It's possible to provide a search string with a 'after' or 'before' option instead of 'replace'. If so, the new text span is inserted after of before the first match, and no text is removed or moved into the text span element, that may receive the value of 'text' (if set). The example below creates a text span with the "Standout" style and whose content is "OpenDocument" immediately after a the substring "the best document format is ": $doc->setTextSpan( $paragraph, "Standout", after => "the best document format is ", text => "OpenDocument" ); While the 'replace', 'after' or 'before' parameter automatically selects the first match, it's possible to reverse the search, thanks to the 'way' option, whose possible values are 'forward' and 'backward' (default='forward'). Caution: A substring located partly in a "span" and partly outside it will never match. In addition, while a text span is allowed inside another text span, a text span can not be spread across element boundaries. The 'offset' parameter is a positive or negative integer that specifies the start position of the span in the text. So, this parameter allows to insert a text span at an arbitrary position (counted forward from the start or backward from the end). If 'before', 'after' or 'replace' (which are mutually exclusive) is set, the 'offset' option, if provided too, specifies a search space restriction and a search way. If 'offset' is positive, the search space runs from 'offset' to the end; if 'offset' is negative, the start position is counted from the end and the search space runs backward from the given position and the beginning of the context. More generally, this method uses the logic and the search options of setChildElement() to compute the insert point, like setAnnotation() and other special intra-paragraph markup insertion methods; setChildElement() is described in the OODoc::XPath manual page. Remember that setTextSpan() creates only one text span with various options; if the aim is to automatically create a text span for every match of a given substring, see setTextSpans() or textStyle(). =head3 setTextSpans(element, style [, options]) Applies a special text style to all the substrings of a given text element that match a given expression. See setTextSpan() for explanations about the meaning of "text span". The main difference with setTextSpan() is that setTextSpans() operates repeatedly so it may apply the given style to every substring that matches the given conditions. The context element and the style to apply are provided as mandatory arguments. They are followed by the named search parameters. These parameters are the same as for setTextSpan(). Warning: the user must ensure that the provided search parameters make sense and can't result in an infinite loop. For example, the following instruction will attempt to create an infinite sequence of continuous bookmarks before the first occurrence of "xyz" substring, if this substring exists: $doc->setTextSpans( # Wrong ! $paragraph, "SomeStyle", before => "xyz" ); Taking the following paragraph as an example: "OpenOffice.org includes Writer, Calc, Draw and Impress" Assuming this text is contained in a $p element, the following instruction gives the "Highlight" style to the "OpenOffice.org", "Writer", "Calc", "Draw", and "Impress" substrings: $doc->setTextSpans( $p, "HighLight", capture => 'OpenOffice\.org|Writer|Draw|Calc|Impress' ); Note that the instruction above produces the same result as: $doc->textStyle( $p, "HighLight", 'OpenOffice\.org|Writer|Draw|Calc|Impress' ); While the 3-argument use of textStyle() is appropriate as long as the aim is to unconditionally apply the given style to every matching string in the context, setTextSpans() allows much more selective operations. The given context may be any element, including the whole document, and not only a paragraph. This last example produces the same effect as the previous one, but it operates between two given arbitrary text bookmarks that may be in different paragraphs in the document, while the context is the document body: $doc->setTextSpans( $doc->getBody(), "HighLight", capture => 'OpenOffice\.org|Writer|Draw|Calc|Impress', start_mark => $doc->getBookmark("BM1"), end_mark => $doc->getBookmark("BM2") ); See also setTextSpan(), removeStyleChanges(), textStyle() and setHyperlink(). =head3 setUserFieldDeclaration(name [, options]) Creates a user field declaration for the document. Allowed options are: 'type' => the data type (default=string) 'value' => the initial value (default="") Returns the new variable element if successful. Does nothing and returns undef if the variable already exists. The example below creates a declaration for a variable whose name="Amount", type=float and value=1234.56 $doc->setUserFieldDeclaration( "Amount", type => 'float', value => 1234.56 ); See also getUserField(), userFieldValue(), setTextField(). =head3 substituteText(element, filter, replacement) Replaces any substring in a given element and its descendants, matching a given filter (regexp) by a given replacement string. It "replacement" is a string, this method produces the same result as replaceText(), and it should be preferred. If "replacement" is a function reference, the replacement value is the return value of the function. But, unlike replaceText(), any argument after "replacement" is ignored. This method is a wrapper for the subs_text() method provided by the XML::Twig::Elt class. See the XML::Twig documentation for advanced details. =head3 tableName(table [, newname]) Returns the current name of a given table, or replaces it with a new name given as the second argument. The table can be indicated by number, logical name or reference. Returns undef unless the given table is defined. If the new name is the name of an existing table, the table is not renamed and an error message is produced. =head3 tableStyle(table [, style]) Returns the current style of a given table, or replaces it with a new style given as the second argument. The table can be indicated by number, logical name or reference. =head3 textBoxCoordinates(text_box [, new_coord]) Gets or sets the position of a text box. The new coordinates, if any, must be provided using the same syntax and units as with the "position" option in createTextBox(). =head3 textBoxDescription(text_box, [, new_desc]) Gets or sets the optional description (long label) of the given text box. =head3 textBoxName(text_box [, new_name]) Allows the applications to get the name of the given text box (which makes sense if the name is unknown, i.e. if the first argument is the element reference or the order number and not the name itself, of course). If a literal is passed as a second argument, the text box is renamed accordingly. =head3 textId(element [, text_id]) This accessor gets or sets the "text identifier", an optional attribute of any text container. This attribute is presently used for a few elements by OpenOffice.org (ex: the notes). With one argument only, returns the existing identifier of the given element, or undef if the element doesn't own a text identifier. If a second argument is provided, its value replaces any previous value of the identifier, and the text identifier is created if needed. The new value is not checked, so the application should take care of its uniqueness. The text identifier can be used as a bookmark, knowing that, unlike a bookmark, this attribute is not visible for the end user. See also selectElementByTextId(). Caution: The text identifiers created or changed by other applications are presently *not* preserved when the document is edited through OpenOffice.org. =head3 textField(type [, options]) Creates and returns a variable field to be inserted within a text element. Such a field doesn't contain any static text by itself. When included in a text container, it tells the editing/printing software to display dynamic context data, such as date, time, file name, page number, page count, author, etc. Variable text fields are mainly used in page headers or footers, but they are allowed in the page bodies as well. Remember that a text field must be attached as a child element of a text container (paragraph or heading) in order to be displayed. However, the text container itself may be attached to anything anywhere (ex: a page header, a table cell, a list item, etc). The first argument (mandatory) is the field type. Many field types are allowed, so they are not all listed here. For some of them, options are allowed or required. To get the full list of field types, and their possible options, look at the chapter 6 "Text fields" in the OpenDocument 1.0 specification. However, a few ones are presented below as examples. The field type, as well as each field option, must be provided as it appears in the OpenDocument specification, without the "text:" prefix (this prefix is automatically added). However, the application can force any arbitrary field name and/or field option such as 'xxx:yyy' (any name or option including a ':' is accepted as is). Caution: textField() allows the user to create any kind of field, without OpenDocument compliance check. So it can be used to insert application-specific markup in any place. This feature could prove useful in some situations, but remember that a typo in a field type or option will not be automatically detected. In the other hand, every non-OpenDocument field is silently removed if the document is later edited and saved through OpenOffice.org. Knowing that the created element is not attached to a text container, it must be inserted or appended through another method. For example, the following sequence creates a paragraph displaying "This document contains pages and we are in the page ": $para = $doc->appendParagraph ( text => "This document contains ", style => "Standard" ); $pg = $doc->textField('page-count'); $doc->appendElement($para, $pg); $doc->extendText($para, " pages and we are in the page "); $pg = $doc->textField('page-number'); $doc->appendElement($para, $pg); The 'page-number' field type, introduced above, could be adjusted in order to display the page number of any following or preceding page. To do so, a 'page-adjust' option, set with a positive or negative integer value, should be provided to createField(): $pg = $doc->textField ('page-number', 'page-adjust' => -2); Note that, if the arithmetic sum of the real page number and the 'page-adjust' value doesn't match an existing page, the editing application should display nothing. As another example, a 'chapter' field displays the current chapter number or title. It requires 2 options: 'outline-level', an integer which selects the hierarchical heading level to be regarded as the chapter level, and 'display' which controls the value to display (chapter number, chapter name or both). The following instruction creates a field displaying the number and the name of the current level 1 heading: $chapter_field = $doc->textField ( 'chapter', 'outline-level' => 1, 'display' => 'number-and-name' ); Other possible fields display the current date or time (see the setTextField() example about a time field with an optional ajustment), the author's name, the file path or name, and many other variable or fixed values, according to many options. =head3 textStyle(path, position [, style [, expression]]) =head3 textStyle(element [, style [, expression]]) Reads the name of a text element's style or, if a 'style' argument is given, changes it. The text element may be a section, paragraph, a heading, or a text span included in a paragraph or a heading. The element can be indicated by the pair [path, position] or by reference. If a style is provided as the second argument, it's applied to the text element (and it replaces the previously set style, if any). If a text string is provided as the third argument, the given style applies to every matching substring in the element, and not to the element itself. In this case, the given style must be a text style, while it must be a paragraph style otherwise. The returned value is a literal style identifier, i.e. the value of the element's 'text:style-name' attribute. This identifier could be used to retrieve the style element itself, through another method such as getStyleElement() (see OpenOffice::OODoc::Styles). In the following example, the 1st instruction just returns the current style of a given paragraph, the 2nd one sets the style of the given paragraph to "New Paragraph Style", and the 3rd applies the "New Text Style" to every substring matching "foo" in the given paragraph: $current_style = $doc->textStyle($paragraph); $doc->textStyle($paragraph, "New Paragraph Style"); $doc->textStyle($paragraph, "New Text Style", "foo"); For more selective ways to apply a text style to a portion of paragraph, see setTextSpan(). =head3 unlockSection(section) Removes the write protection (if any) of the given section. If the section was key-protected, the key is removed and provides the return value of the method. Example: my $key = $doc->unlockSection("Section1"); $doc->lockSection("Section2", $key); The two lines above remove the protection of "Section1" and protect "Section2" with the password which previously protected "Section1". =head3 unlockSections() Removes the write protection of every section in the document. =head3 updateCell(table, row, column, value [, text]) =head3 updateCell(element, value [, text]) Modifies the content of a table cell. In its first form, indicates a cell by its 3D coordinates, as with getTableCell(). In its second form, indicates a cell by its element reference. If the cell is set to literal, its content is limited to its text. In this case, the optional argument "text" is of no use (the text equals the value). If the cell is set to numeric (float, currency, date, etc.), you should generally pass a literal argument as well as the value. This method can be replaced by cellValue() which allows reads and writes. =head3 updateText(element [, options]) Changes some text content in the given element or its descendants, according to the given options: 'text': a text string that will be inserted somewhere within the element, at a position which depends on other options, by default at the beginning. Alternatively, the 'text' option may be a reference to a user-provided function whose return value will be used as the text to be inserted; this function will be called with the document object as its first argument (so it can use document-based methods), the current text node as its second argument and optionally with a text string (that depends on other options, see below) as its third argument. (Be careful, a "text run", or "text node", is not always an element; it may be a local text segment within an element.) The user-provided function is not supposed to update the text node by itself. If the return value is undef, or an empty string, nothing is inserted. 'replace': a search string (regex) whose first match will be deleted (and, of course, replaced by the value of the 'text' option , if any). 'after': a search string (regex) whose first match will be followed by the new inserted text. 'before': a search string (regex) whose first march will be preceded by the new inserted text. Note that 'replace', 'before' and 'after' are mutually exclusive. If 'text' is set with a function reference, the corresponding function will be called with the matching substring as its second argument; if none of the allowed search string options is set, this function will be called with the document argument only. 'way': an indicator, whose allowed values are 'forward' and 'backward' (default 'forward'), that specifies the search way. If 'way' is 'backward', the provided search string (if any) is searched backward from the end (i.e. the first match is the last match in the order of the document), and the 'offset' is counted back from the end (note that a negative offset automatically switches the 'way' to 'backward'). If the result doesn't depend on the search way, the 'way' option should be omitted or set to 'forward'. 'offset': a numeric position that specifies the point where the content of the 'text' option will be inserted; if 'offset' and one of the allowed search strings (after, before or replace) are provided, then 'offset' specifies the limit of the search area instead of a insertion point; if 'offset' is positive and 'way' is 'forward' (or not set), the search is done from 'offset' to the end; if 'offset' is negative or 'way' is 'backward', the search is done backward from the given offset to the beginning; a negative 'offset' means a backward 'way' (but if 'way' is 'backward', the sign of 'offset' is ignored, in order to avoid useless complications). 'repeat': if set to 'true', specifies that the action must be done repeatedly in a way that depends on the other options. If 'repeat' is set to 'true' while none of the search string options (after, before or replace) is set, the result depends on the 'offset'. If 'offset' is 0 or undef, then the 'repeat' option is ignored (avoiding an infinite loop!). If only 'offset' and 'text' are provided, the new text is repeatedly inserted with 'offset' as the interval. Note: for repetitive and unconditional text replacements in a given context (i.e. when no parameter other than the context, the search filter and the replacement string is required), substituteText() should be preferred (see substituteText() in OODoc::XPath). The following example replaces the last match of "old" by "new" in a given paragraph: $doc->updateText( $paragraph, replace => "old", text => "new", way => 'backward' ); The same, but the replacement is done for the whole paragraph (note that the 'way' option is omitted, knowing that in such situation the result will be the same whatever the search way, so 'forward' is OK): $doc->updateText( $paragraph, replace => "old", text => "new", repeat => 'true' ); The next instruction makes sure that "Smith" becomes "Mr. Smith" everywhere between the bookmarks "BM1" and "BM2" (because such a space may spread over various and multiple elements, the whole document body is specified as the search context instead of a paragraph): $doc->updateText( $doc->getBody, before => "Smith", text => "Mr. ", start_mark => $doc->getBookmark("BM1"), end_mark => $doc->getBookmark("BM2"), repeat => 'true' ); =head3 updateUserFieldReferences(user_field [, context]) Forces an immediate update of the display representation(s) of a given user field, according to the actual value of the field. It's possible to restrict the scope to a particular context, that may be provided as an optional argument. =head2 OpenOffice::OODoc::Element methods While all the methods above belong to the document object, some additional methods are defined for individual text containers. These methods belong to the OpenOffice::OODoc::Element class. The most general of them are described in the OpenOffice::OODoc::XPath manual. Some of them (listed below) are simple read-only accessors allowing the user to check the type of any element. =head3 isXXX() methods A set of "isXXX" methods, returning true or false, allow to check the type of a given element. Caution, this methods belong to the elements, not to the document. Example: print "This is a list" if $element->isItemList; Here is the list of element type indicators: isBibliographyMark bibliography mark (in the doc. body) isCovered covered (invisible) table cell isDrawPage presentation or drawing page isEndnote endnote main element isEndnoteBody endnote body element isEndnoteCitation endnote citation element isFootnote footnote main element isFootnoteBody footnote body element isFootnoteCitation footnote citation element isHeading heading isItemList list (ordered or unordered) isListItem list item isNote main note element (end- or footnote) isNoteBody note body (in end- or footnote) isOrderedList ordered list (OOo only) isParagraph paragraph isSection section isSequenceDeclarations set of sequence declarations isSpan span element (see setTextSpan) isTable table isTableCell table cell isTableRow table row isUnorderedList unordered list (OOo only) =head3 Other element methods For a neater and more direct access to element types, see the getName method of XML::Twig::Elt. A call to $element->getName returns the element's XML name including its namespace prefix e.g. 'text:p' for a paragraph or 'table:table-row' for a table row. Remember that all the features of XML::Twig::Elt are available for any text container. =head2 Properties No class variables are exported. Instance properties are the same as for OODoc::XPath, plus: 'delimiters' => delimiter table hash giving the relation between element types and the delimiters to use when exporting text (see getText). 'use_delimiters' => delimiter usage (see getText) indicates whether delimiters are to be used by getText or not when exporting text. Set to 'on' by default. Can be set to 'off' or another value to stop or limit use of delimiters. 'heading_style' => default header style indicates the default header style to be used by element creation methods when no style is specified. Set to 'Heading 1' by default. 'paragraph_style' => default paragraph style indicates the default paragraph style to be used by element creation methods when no style is specified. Set to 'Standard' by default. 'field_separator' => field separator contains the character string to be used as the field separator when exporting tables. By default it is ";". 'line_separator' => line separator contains the string to be used to separate lines when exporting "flat" text. By default, it is a line-feed ("\n"). 'max_rows' => max table length (default 32) 'max_cols' => max table width (default 26) these 2 properties control the size of the "managed area" in a spreadsheet; the default "managed area" is the A1:Z31 rectangle, corresponding to the (0,0)-(31,25) coordinates; see getTable() and getTableCell() and normalizeSheet() for more explanations. 'expand_tables' => table transformation usage indicates whether the XML representation of the spreadsheets are to be expanded in order to allow the same cell/row addressing scheme as with the tables belonging to text documents; by default, this property is not set. If this property is set to 'on', the first access to any sheet will automatically trigger this transformation, so the explicit normalizeSheet() method will not be needed. However, this automatic (but costly) transformation has a drawback: it uses the same 'max_rows' and 'max_cols' values for every targeted sheet, whatever the really needed managed area for each one. =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2008 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/Document.pod0000644000175000017500000002227611063652705016060 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::Document - Top level component for content and layout processing =head1 SYNOPSIS # get an ODF file handler my $oofile = odfContainer("myfile.odt"); # connect a content-focused document interface my $content = odfDocument ( container => $oofile, part => 'content' ); # connect a style-focused document interface my $styles = odfDocument ( container => $oofile, part => 'styles' ); # process any content and style element $content->appendParagraph ( text => "An additional paragraph", style => "BlueStyle" ); $styles->createStyle ( "BlueStyle", parent => 'Text body', family => 'paragraph', properties => { area => 'text', 'fo:color' => rgb2oo('blue') } ); # commit the changes using the file handler $oofile->save; =head1 DESCRIPTION This module defines the top level Document class, which is a connector allowing any kind of content and presentation processing. It inherits from OODoc::XPath, OODoc::Text, OODoc::Styles and OODoc::Image. The most usual instruction to get access to any member of a document, with the exception if the metadata (meta.xml) should be something like: my $doc = odfDocument([options]); This constructor, if successful, returns an object that can be used (according to its "member" option) to process styles, images and text. This module is designed simply to create objects which include all the functionality of OODoc::Text, OODoc::Image, OODoc::Styles and OODoc::XPath (which should not be called directly by applications). For example my $styles = odfDocument(file => "source.odt", part => "styles"); is generally better than my styles = odfStyles(file => "source.odt"); While OODoc::Document inherits all the methods and properties of these classes, its detailed documentation in essentially provided in the following manual pages: OpenOffice::OODoc::Text -> text content OpenOffice::OODoc::Styles -> style & layout OpenOffice::OODoc::Image -> graphic objects OpenOffice::OODoc::XPath -> common features & low-level API For example, the appendParagraph() and createStyle() methods used in the synopsis above are respectively described in OpenOffice::OODoc::Text and OpenOffice::OODoc::Styles. The present manual page only describes those methods (there are very few) which combine layout and content processing. =head2 Methods =head3 Constructor : OpenOffice::OODoc::Document->new() Short Form: odfDocument() or odfConnector() See OpenOffice::OODoc::XPath->new (or odfXPath) Returns an OpenDocument connector, available for subsequent access to any element of a well-formed document. Knowing that the Document class is a derivative of the Text, Styles, Image, and XPath classes, ooDocument() implicitly executes the corresponding constructors. So all the options of these constuctors are available. If no "part" parameter is given, the member selected by default is "content" (see OODoc::XPath). The most generally used parts are "content" and "styles". =head3 createImageStyle(name [, options]) Creates a graphics style which is immediately usable. With no options, this method applies to the new style a "reasonable" set of characteristics which match fairly closely the default image presentation style in OpenOffice.org before any manual changes made by the user. An application can set its own options in the same way as createStyle in OODoc::Styles. The aim of this method is to minimise the amount of work involved in setting up the style, especially when the default values are close enough, and bearing in mind that an image must always be associated with a style to be displayed in a document. The code below shows a simple method of inserting an image into a document, in this case linked to a given paragraph (see createImageElement in OODoc::Image): my $anchor = $doc->getParagraph(4); my $style = $doc->createImageStyle("Photo"); my $image = $doc->createImageElement ( "Eiffel Tower", style => "Photo", attachment => $anchor, size => "4cm, 12cm", import => "eiffel_tower.jpg" ); The 'properties' option is available for customizations, according to the OpenDocument naming rules. For example, the following instruction creates a style for centered images: $doc->createImageStyle ( 'Centered Image', properties => { 'style:horizontal-pos' => 'center' } ); =head3 createTextStyle(name [, options]) Creates a text style which is immediately usable and whose default characteristics are the "Standard" style in the document, even if no options are given. If the "Standard" style does not exist, a "reasonable" style is still created (this can happen in a document created from code and not by an interactive office software). An application can still pass all the options it wants in the same way as createStyle in OODoc::Styles. =head3 removePageBreak(paragraph) Removes the page break from the given paragraph (before or after). This method actually removes the page break attribute from the corresponding paragraph style. It does not remove paragraph styles which may have been created to carry page breaks, so its effects are not technically the reverse of setPageBreak(). Generally speaking, however, this should not be a problem. See setPageBreak() about the logic of handling page breaks. =head3 setPageBreak(paragraph [, options]) Places a page break at the position of the given paragraph. By default, the page break is placed before the paragraph and no changes are made to the page style. You can place the page break after the paragraph using the option position => 'after' To use this method properly every time, you must remember that a page break is not a text element, but a style applied before or after the paragraph concerned. Putting a page break in front of or behind a paragraph actually means adding a "page break before" or "page break after" attribute to the paragraph's style. As always, a page break cannot appear in the text in keeping with the principle of separation of content and presentation. This however adds a slight complication, in that all paragraphs which use the same style will have the page break. Otherwise, if the paragraph has a named style (i.e. defined in styles.xml) and we are working in the body of the document (i.e. in content.xml), then this method will not work as it cannot access both XML members at the same time. There is however a solution (the one used by OpenOffice.org) which consists simply of creating a special style for the paragraph which takes the old paragraph style as a parent and has only a page break attribute (the old paragraph style is not modified). To do this, all you need is the option: style => style_name This option forces the creation of an automatic style with the given name (make sure none other exists with the same name) and which will only be used to carry the page break. Later on, you can of course apply other characteristics to the style using the updateStyle method in OODoc::Styles, but this is not recommended. It is better not to use page break styles for other purposes. The nature of the existing paragraph style dictates whether or not you create a page break style. If the paragraph style is a named style (i.e. defined in styles.xml and visible to the user), you must create a page break style, but if it already has an automatic style you must not. The quite rare but most complicated scenario is where the paragraph has an automatic style shared by several paragraphs. In this case you must then make copies of the styles using the methods in OODoc::Styles. A page break can allow you to change a page's style. You can do this with the option: page => page style in which you give the following page's style (i.e. the logical name of a master page. See OODoc::Styles). Remember that if the "page" option is given, the page break is forced before the paragraph (the "position" option does not work in this case). =head3 style(object [, style]) Returns the style name of a text or graphics object. If the first argument is a "master page" (see OODoc::Styles), it even returns the associated "page layout". Replaces the object's style if a style name is given as the second argument. =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2008 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/data/0000755000175000017500000000000011416644377014506 5ustar jmgjmgOpenOffice-OODoc-2.125/OODoc/data/image.png0000644000175000017500000006542311320633442016271 0ustar jmgjmgPNG  IHDR,&sBIT|d pHYs&:4tEXtSoftwarewww.inkscape.org< IDATxwxlOB]i "" .v6,W+D ^BBB H^Bny| sμgΜB,˂ qOm=E`C Z^mAGP$$, ӡmMۻAJ#WC.86Ѵb$9$a8kwAcsO"G%=$a,' `=VSEJH"n+ {̢:Gw|[!B"pC}:\#u^EB&>"~`]zߋ{ $aDns ~[U֍!*E!WAN877ZF@M! Y3ౕ7ZEC51Q' ",ǿDLVj1QT"X=VSyu6IX!0m2T^ș B@ð!.:jf3B CR z'DAc~,8L29QE&"Κm ¯(i '˱PiɊVg@6B"aO Þ`;,vԭuN.C{BD 8 Gء\DƯAAX~ӑ7#v0uIIXDD`zw{_bRE[BE=wm>)v( Ŵ%;@V8c=ثᶝV n[m ʎrȢA ?c;Ѵ ] rE|!ݦX#lX2km+{ &2%% &uALC<6}]d)7--멟` mBv@U$aQ-z7Qʔu߳s@.H m9u0*9VBPf2s<C:mr$ae(D:b(F M Pw}e.V87!:@I#hEk-QX(GC9kWWZ$ac+Gkඞ;DC|yw]u2)(Ofi#0hqXKðQ" >@> qP%}BuQqLPoGV ˫ǻ.4@F8{Iz:b{}P5JjT(F E2Cq=(y\mqBz0x;Mc?7 2YIy32r$}X DPfq)ZK]̨3퇵 =yBot}) lF !`;aD<7"PgLFPnIX!b=G;uðQx:D>¹B\o2N W6((F@.(FTnH {*?%v͒e-;̹"e(; Ctm:e-1H@ ̂&0QmCrM̩ YZ3=18a?_פ3Y,Q]@V'˿U`-S>_SޟPъDu. hZ 'P )ax5nY[B`,ƻXڏe'Lw`%,ZH m.VMϼ~bi8pT`+?x@X;gYKHFTsxoC1%,kɏӡnua%4でa/S0&0N-V _=,BQ KPrBD]ip[p*/Ou'a+Vd.TYf$b;Cscnm hU:zoO6x~'H=Ms `)SYJ.&R2~sylͫN(P((E$ae8 |} ʪZYE|o u(s $am=֛: bB8`0O.&Oʇ"u!AX7m='v(]SYUI>Je&߯ O/vunt pK|hDu|")B֐ex!"96p;CjjrKh=3bPIX8kwAsvMtmNF1J0f{0֓ % Cn!GZNeѹugS |sOp[9>X-aثpD`eg\{lw̼s FXջ ء"t2k(y:{֒p[ ap1a=º1 N#!Df= b|M1*XEnG* :'XHO^J0 .]8$+'=DR@Jè$+xe0#H[F+SA$a]v0~]0&H;ui+(;ur={iN4| 8*Q(p߿>H‚wI;$48!u~w[Nx_FO5iFBnK)[o2 !aE~daXIxOAQC3fjfTA°8!qۓBnqy\fY&,c˰.; Bi#+[qhYQM Ҍ*,ǿc $N4?~=T3P|saM1x֒J,$acn8j0Z!\·rKr[$-!.ZKD]|]>BT"#=c]onؑFɢ?hdx׵o!*QZs`G0 h%ybu' |c.~; B(} EPUr>>|I35H3XOӑ!iz}e5mK`0lV9*1 $.ju z`AJj\.C!$,ӿi{zʵl~(KB$"rbBH&aDu?ݶi`=h1->ay:NV)v(g \˿HKp]TbBH$xw=[oj=wrK(0î{&v$)ӯE\/x?r 2)l_]0 S$B\{ nqhLQ%g\0 @m9 dLa;,DŴG`{Zyenl" EQî 1H "W=ʛ% ,ܐV`\"ok^sa^hU,UBFLi;E*z̅~AѠ`-h@[Qg{uKЪu2m> $IGlwjy3J~!(yhya4Hr 8@cpĤJY\g:7 |pTo2,.Pg-vfqPb$lChŲ,(gn8^fPj;̻qӰ:EU^0DV.[\GzG)"kS+0" [H@ɢL ehVGZOAb2b˻mxbZBH+XU`=s+R#?`sA# SKH&v " KD3`=Lm"y-(yճO)Ȗ):=}8<ǫ! YL{{I IXD1Nec̅:{mg~aS ߷}FNMtLdO?w?@:,6# $aQ4~YL[hލNդovn!"$e-CH"Db ju bͫ-GzwxBH̼aRD(&| ʌ)|J;kA6n{p$|dQCFoPD4KQ>8ܩ)` FHuaUOqe *.}10Y|7$ [DzXo䜋EnH(=>d1,B`nVJi@X&%$㶔Bi ܖ3b!?{8!A$YV xde>!v( 7>;$a8unء"j.t[nKPPxEux\0$,/ )pjw W%D*E+Et$a5sn ?{?bB4 S{^0$$fu[a>,ş? )qDt#$i;X}Bp(ӯCl@+SErHj&<2bC4b43EHŠd L%Lq}VP$$Y3`?;<"@JDuxQ(v"t|8Qn9C"8Pe݈.4ˁ" +ܰ /%*ŎA1ހ<ءS-pQS/vTMKDw~; IXú-pjI9Jsɹ;FX\pgu>;2B:wC="Ap"IXº2S.~8uuŎ<4aL IDAT*ƈI (Y7IeM"m(LI[D',ct[2OLb`#$BAP$<'y'1aSW`4!O ybBZP"lE\ºQϊ>oKyA1V8"Dl.XN|ӑ|P^QyztDd'b 6A4z0~ kY@-1VP uGDca:uvXO|Gnu(P2}dq]#Au4߄u(UG6%ӀVVVAIbKIXA r-Aa$, IXA $a6H""lED AAa$, IXA $a6db@̀'jPVRC6N (ⓢ& Y9IO;\nb=pp*@T#S[ađQuVm&-ucp tZ ew!l>3:u&ېp]:n(/ٺ0t&aJ@T ىHo EB~]>:jYp~r}p15\C gWƣ?CZ!Ғ229]gˀl(\K e,EARgO[]kccU;a5;] qlxIRfSVSqK9h g],t?N6abUwe@v׻\s·YGۃY+赃Szų8=߾V AdDD%,pT]'.9 |q T]ћ0z6hLxuc& ٳ wi E8)h,{LYna0u"^?. ZS$I%, ZuHſ܉#rys;j3FmsX{,3A:ȿ\,%^lO:sz^'DQ5_ܦcnf$<4vdqpȠ7Fk7)7`?_:]TҶK<6wlHdIj PqXr9M\>o_s".ak`OUܿ5Z2O|rr{jژ1>g/Uwo|屡!IaC,òeMc kn峧ZSnu/" e*n uESwmL3}+`>NɲXCa0j6\;l;V6^mjИHm]k =@;gr= @lncػ%m?ϟ[u7;T ۂX0,K,lL&a3UbҸ7(0vf?|ߍ)9Rm1 d%#;&v8$f1/?úZ23 3Qsx{0jj\.nn أR&Oz mꂞC,SOM[*":af@ 4 ~:iHɌMoz˚4Dǩ,,iPly,JGvpQ V$@Vi^ 0 Ղ\ˀlt) )=2'kˈ)t>>PEL’E"qj$ Zzv'tSPn('#vF'?9NTm hFvb"iXz&3л*NU-$""uQF5)Pte#.Am %`v:R-3_MM݊|mRu6VM',}Mpo), ,]J1*D=u 8j#UqNC8v2R%Za ]СWK/RoT .VOo鰹q/KB: Ezr: 6n3 v 떕iw45)#?Ajx\=+=J5?2qc[ja`ˁh [B q}ͩԸ/'UMxi)>c~~hvBO0(w: &Maw&f&l{XPT`[ ޜw7}> ^C _ZF"1Ӣg|] C%`˄嶜z јp'ܤNӟOº HDzޝ_e _T-şp.ۢ]2X4 9]}ϐ/390ꬼoYNmpޮHJ0o|lK8K+ c8%QS{s}NKFݞ.7QCI]0xa܎Rd斞斞8}GV?n~t]fcֲ]NW/п+.8ɨmz#0aY ]qҤ^ xH'ZuHŌ?L)=띖촻ppI,|o~pCC w 1 ϓ,_ Rw`R4iE_X',pT~L_ h ]d૭/4xromci ?B_m__rVjʹ M_?UJ]'AַVBc|JYpF5<E{b盱wñ:e*KuMt{{,행0%mZފ{tGA崻pDAK|jo}? 5B,.5kQIbxo4VNL Q4W\"=ՕRvkzUvtc$ ܖn행pT =;8l4C?w+!ȫho+_qKق ]BM GzhLFګ`;` T/ !U96C5`Y`ܖ/ O7,3hLD"+c&(T2 =D5O.p uj j&j0nv#OAm|>mۼL(kþMǛ,ӶsFXHk+v_> -DӚMº?v~1rJOIb;q~>yYN7Yn~A#5f6gkD*SIf.D6=k!*#nvk0 $LwZEٺj{73[`4x}Ϝ/Hlxlom]7O D#q|;yLmQW棴yWpۓ#8/' ʴ8]Tlf$һ f[";![j4u`^SS^ r`[”PzwϊahqsE)pTt^uc3W=1(۾R[n}|}.6wb9;_uB8> s|S#AB5aQP[zVGp;:s[{ ڦT*s>L?qybQH Yl'j+ p:59[|ۓ#0`Px QcSz/AvTY7B[S ;LbԙP "u)CA{j ZuH-sS7܀ K`?GQrW 9]3TG\=k[G M aHA`(Q"e2$"kf7ߓ+dԯe^PHŲ\+e*K.anGyQ(Y,u:hU&d9`cۃ >?6UmѪCV>j=^ }6N (S"'-ۧCPG7!&, +H $a6H""lED !QɎ/lUu'w:l.;pi#Jv2#A z:S\W/u-_]S>M>~VA k{Cn5s>ԠB NUY4vV\G߀ݽ߽k1] g2h-:ub:9{k03k"bAt3=a~zw- iƘV|ҟV.tG8"D w5E3sh6g.yGc{:*6Q^>8 :Zvݳ.ע۾*xk\[?o|صyW븵JA)xיC\ IDATbA푅bW[/=<-s}zoWym tGAct"ug,T۹t+=`vqF:ng}'aٗ[c:O :.xF*o iwġrh+0h- %+9]2ۜ8UXr#fx\&j6yAGU:VrWւrjʍVAhDŨ"yiA7P0ԚaZTAHJ!wӉO6]|O7\d5<٨W  G^٩ZTBWmB\i1h9CRAttQ%j+V Wʐ1ٝ>SUr>׹=[jK[i/bOHI0!Pż|6tF]Ob50:~ij]ZQKA=QS}O7Yv|{c0jrZDa0M47Vcߦy/#KX!4yێGu7 +{v/̛`',ͅs'/Rj = seU&,r+8?``g<8. G+Mѡun_U+y7B6kÏoc5;}U!*:rV?8l;~V剸!NyVfiM"_yP9޺UT:>g*| m /Oh0t Չo^YEm|:׭4l2Y'k,&+Z<blcQzǂFR-@GX5^(( r'7?ru~rkk{:eFNC66V4( XqN_dYJWOhNXe:cK.mp= CEJCu>޾W^>̟֍/ڥ U(¹8^PEoDK=şnBVd Ӊ9`2&bNKCvGSe+4&=vh9Ym/޺XvTP˾Z~?Grѯvo3 7uoހƏjl|A~ :Ƙd$#!%V +w1,flYw!&>gplw!.[KfC`0տ|da4?7yah]f]L(9\{J;n?x= ϻ)(?ŗ/q뎽[dX 6:ZS p#]\_g,#6Q>#ڣψh.^QSfO9xޫIV1j( ץ^)h~v7嗞}2 DAՉ#;Oz oFIFϡMLƌ)=](// #oo^şlj~FD et巃ڄ\V'Ym{^S6OD>-ѱOK>gt ? = 6 >ڀ]2Qp߿ohQWm7/WG+rf"k&팷[Nwa>B3U o2az M/[B>;ymL)W3u/j+9v6,=P=du9ugv4Nf5ٱڏ댧?«0lrwяuyC3f`uDR̗j-8m 6I/X6|u^u[t`~Π\՟7zqQxn/r#܈ wOV\DD¢ Sٱ3Ym'wH(G{FFdž/W0ۖ[OZ /V6qjx,.+x ~z_tI@i0 u]Xh_)R-74b?E|v21`t#"a .*/7l_}V;)xAYu #ʚGҰIݑ"5ѶKFx $ዷUViWr`)Ab)q d)o5={tym]kXzm? .KŕIHX&uW~rVnu+wtoiUI~e~=*վHɺ c :di- W0>QP+4Gi-q3#>~ػe0 \[V1 n"ESx=n=[)web/_S|bVSٕR2'+9.tΠ;!e((UquTުZj{kjZ{:QDED-{H$ >AjH iB_(I|æg򐅚`<UM2 m+cdCT }1DG%v`n&<{9ۥըIt%N6elzǂ GW[.Ǣ[(]5>?ZjK QB}zQ.&ZJqrMCuu!kPU]F~^=k[[ - s,e )'-=V+wKe섊,V /{`i أ 4Iex 7,:|!<5 Z\(D l=v;!/S1[PW ):= oZN5ev_=Y[+#<[gCFoY~Ź/ tI14S_~&̖,,9䠩uBӽl  hJJߖbcOf1iOi~>HasXxe!>I*| -ǖ(B{:UY,S;X7Xf9+jJddDTlrd 1ffm7AXf@[gcEL-,9f=a(4`UhH4fX`>M .R|]Ak0T+l1xjet|LR[6SAcg>*ԕQxH`x> MV|+\k}RSi\tuΤG|;CIK ;j\Sd>(Y7TTr WhҊhz#b6oXpش,uzQdf0wM؞?}vQ%[BPF^uwC_ ŧ+drJlmҊwVHSc3bw5%gW :S Ue|x3V!GOm-Fl=ƮwOv! GC'F-LwD+p|w<:'7SFVWKoNq7c,!mlV)).i y6QNpS?1&|_L8Nr֖Fo ǂ~`ǛB`GYm[ѮcL\E[q)\Au䥗4\?byAkaDN>T-E5x94L&ljܠ)佉:37'sCv}j4Z6CiA5~+pwhU/U1Urpk4H+G&@p7BPS({ZMw֫$ѾXi*n]5,n7Ϋ8:z{";`Yy(+vzシbx1C55qB}z٩M+%j3W?0jG[g"vtAX$TA|-? _ lylyPW#Fqn6OmEDqQ;肠8$)ŴnW>fPhJl2p{/f/}\ ,C^z)1`M0_~F<ժD1y`Z]̉=vyxdryҡP( fUhݗ˳Ě5j;?ħ+O*iz{vl:bYXsKHhB/gbK-i#|Xv{s1ڨhbK)2ѽɗ*]cb,?e1>?o>&G[9,,zg<_!C.,1db#%#G>6^&?b)hmƪ~C//M9,<3k_NSv,Ķܪv|L ]E2[6v:f %8 ļxRO,}XM͈KG 7i,Rǐ-n>],C uJՃ"aFiqe>=Vw \=bT@\/@D탑齷󪐓R::i%:лWWG|PYC"n_#|Db.stiXM<%Ѷl$L~qA']//&գ3gA%ߩ p[8w0s{a4ʑ;o/0CV%-@Ĩ@>Jk!φi5vNU K.o G+.ulb׶ Gf98||o.y@?,}o(η#z+6NQI{]^Tg;4EwL(|$V~<^W#ơ//$x8aݞh,L^<6H6P+غ²Zp_~肗=i{Qf=,ÛFI[镉nnwGU:r?>o?:X@ME=x㍓ꭩLJ ~ .VMKLPTc}I;W/Nb ?F*έB߫#0)a v;)rUmP:"M7P֊ ;Jr^r*B.gⓗcNm$^7bM@)6,n8Q'TU(ʭBr:x" guC@_456#'eqŘq8S(ͯF"as aW?,a(- x:B>*ENZB[΃fXlxs,ZbxYOWLpxt'1oVW]"%_+FgcqzWO$Zgpxe`}毉xX"見vtkMi+ ~j́D~ukر&x9lZ8 _=E^@HQq~wC\ h.W}OGbSBMkVF.S`߉6^Xʒ[.C`'~77Nn\@9YX P.*L]:Ͼ #Xlȇ }N|s[\%_oӺ(^!CTmM2x,XT->.UU㱣w9؞;P]/ǶVtw=`iZ6s1͘sL bb5;g?L&Dž#(8.VGi5-ќ)XlVn7qwʒ$O"%$ R!h[8NZpX$fY;*&.oDZaCʥNZaS[KR6@】υm'k-Ju؆ϝ _żգw0jz8mWo{SFy:_MV 8.4&#?%KTbnÎfSnT?TgKZ5Jhm RL4\3V k (|Be %ڦ.bԹ́8&k!k~T,i ><ѾJG>PGMtfz#3ՙN=.CY9Ϡ86#ԋʌ$A k6be! (C;p`"X,h,>{ohzĔ0Q Mu$2j>my:k/::ܠ3 rŶӫ0vN> Rx=4lg#FXTǂ1ȏ2fUTS*TӡƂqy֒i,=;ksx~$^@LGh(/=571{$,~ZKAVakONIM5RM7+1=0i LZ4RI32$јs}y`NNǥ@TE&j˰ȵbH_)85~9mEZ(6UdfPFqe&Г@,x+[/Ǟo`Jzف.vz=6XRI3N}KJp8l@_w߄g9+jExK5k*1;M2抍5-$A|N]M@u`> {- ے)3:7 ¬ ַeNOng˥1.$U6?\mєhcg 2qoB Rb>,ؾ[J\HA](ބD`?tbI*þgq$1nemٯҹgCk&Ӗu!G5JcIZg{Jlsh[ۗ+)i@7rhxzwœ_SWzmc:yx3Ȥcn7bhbX$莫CBMZ=u("U\׈]lW>ұ$7~Kţ;vݍ@Xs䔗&LYW@ NB=$N+ VH9>B'b G qtŔŃK$מiH~u?n@Gz딴,㉶|l}AFq.|_&֐! ٽ^{qY\_N=d`ǯ{n1nw4ZBvJNMw*k[XS'EL9lD4AXVwg|GkWR-&+L+o5cqZ Zoj>uGYIs!&]YϦ;'?݈B[>j%($_گhmԙ4mcgq"q@2Ľ˙X?;L[6yYK/E*Wo{8 hql=Pw< _}R&,><#!$Ξv[Q܄j1 *^׳v'[|K&-|bW3]mfG"1B-\ p  78؂cFT: K^l:?Q2VzRx6Vb<-)=-eX,&]TEJXEbq*^tfamc |Nށ.rb[[8w.茴 t/99uO˙K/j|0bZ?faqV q.^v3^ v%<5PP-,@=|q//;bWåm=KH˿<@\~Dh_ 'D\6 {GvJ1Tbeu5d?ARz;Iv}.<1Emtj]YOQSQP x:o, QSI.6ptڂ(,GEQ Esށ]i(BX^QU&XX֞w?GUkz2 TWԡBYH؊k5ca1001X fc`100 b``0`60l` ,1X fc`100 b``0`60l` ,1X fc̆K Y IENDB`OpenOffice-OODoc-2.125/OODoc/Manifest.pod0000644000175000017500000000724611063654325016050 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::Manifest - Access to document file descriptor =head1 DESCRIPTION The OpenOffice::OODoc::manifest class is a specialist derivative of OpenOffice::OODoc::XPath allowing access to the content descriptor of an OpenDocument or OpenOffice.org file. For ordinary content processing applications, it's not absolutely necessary to control the manifest. Member imports or deletions (ex: createImageElement() in OpenOffice::OODoc::Image, raw_delete() in OpenOffice::OODoc::File) may make the real content inconsistent with the manifest. Up to now, the OpenOffice.org desktop suite don't worry about that. However, OpenOffice::OODoc::Manifest provides a few number of easy to use methods to get or set any entry in the manifest. In addition, it allows the users (at their own risks) to create or update any kind of special entry or mime type, without control. There is no automatic consistency check between the manifest and the real content (but this check and others could be easily developed with the combination of this module and the other ones). The manifest (i.e. the "META-INF/manifest.xml" part of an ODF package) is a special member that describes the MIME types and the full internal paths of the other members. =head2 Methods =head3 Constructor : OpenOffice::OODoc::Manifest->new() Short Form: odfManifest() See OpenOffice::OODoc::XPath->new The XML member loaded by default is 'META-INF/manifest.xml'. Example: $my manifest = OpenOffice::OODoc::Manifest>new ( file => 'document.odt' ); returns a new object which represents the member list of an ODF-compliant "document.odt" file. =head3 getEntry($entry) Returns the element (if any) corresponding to the given member. Example: my $element = $manifest->getEntry("content.xml"); Returns the element describing the "content.xml" member of the file, if this element is defined. =head3 getMainType() Returns the main MIME type of the document. For example, this type is "application/vnd.oasis.opendocument.text" for an OpenDocument text file or "application/vnd.oasis.opendocument.presentation" for an OpenDocument presentation, or "application/vnd.sun.xml.writer" for an OpenOffice.org 1.0 text file, etc. See the Appendix C in the OASIS OpenDocument 1.0 specification for a complete list of OpenDocument-compliant MIME types. =head3 getType($entry) Returns the media (mime) type of the given member. =head3 removeEntry($entry) Deletes the named entry. =head3 setEntry($entry, $type) Sets the mime type of an entry element. If the element was not previously existing, it's created. Returns the new element in case of success, undef otherwise. Example: my $element = $manifest->setEntry ("content.xml", "text/xml"); This instruction first creates (if needed) an entry for the member "content.xml" and sets its media type to "text/xml". =head3 setMainEntry($type) Sets the main MIME type of the document. Risky; not for ordinary use. But nobody prevents you from giving a presentation or spreadsheet MIME type to a Writer document ! =head2 Properties As for OpenOffice::OODoc::XPath =head2 Exported functions None =head1 NOTES See OpenOffice::OODoc::Notes(3) for the footnote citations ([n]) included in this page. =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2008 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/Meta.pm0000644000175000017500000003222211415443233015005 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : Meta.pm 2.017 2010-07-07 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2010 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- package OpenOffice::OODoc::Meta; use 5.008_000; use strict; our $VERSION = '2.017'; use OpenOffice::OODoc::XPath 2.237; our @ISA = qw ( OpenOffice::OODoc::XPath ); #----------------------------------------------------------------------------- BEGIN { *author = *creator; *version = *editing_cycles; } #----------------------------------------------------------------------------- # constructor : calling OOXPath constructor with 'meta' as member choice sub new { my $caller = shift; my $class = ref ($caller) || $caller; my %options = ( part => 'meta', body_path => '//office:meta', @_ ); my $object = $class->SUPER::new(%options); if ($object) { bless $object, $class; $object->{'body'} = $object->getBody(); return $object; } else { return undef; } } #----------------------------------------------------------------------------- # overrides getText() & setText() because meta elements contain flat text only sub getText { my $self = shift; return $self->getFlatText(@_); } sub setText { my $self = shift; return $self->setFlatText(@_); } #----------------------------------------------------------------------------- # generic read/write accessor for text elements sub accessor { my ($self, $path, $value) = @_; my $element = $self->getElement($path, 0); unless ($element) { return undef unless defined $value; my $name = $path; $name =~ s/\/*//g; $element = $self->appendElement ($self->{'body'}, $name, text => $value); return $value; } return (defined $value) ? $self->setText($element, $value) : $self->getText($element); } #----------------------------------------------------------------------------- # get/set the 'generator' field (i.e. the signature of the office software) sub generator { my $self = shift; return $self->accessor('//meta:generator', @_); } #----------------------------------------------------------------------------- # get/set the 'title' field sub title { my $self = shift; return $self->accessor('//dc:title', @_); } #----------------------------------------------------------------------------- # get/set the 'description' field sub description { my $self = shift; return $self->accessor('//dc:description', @_); } #----------------------------------------------------------------------------- # get/set the 'subject' field sub subject { my $self = shift; return $self->accessor('//dc:subject', @_); } #----------------------------------------------------------------------------- # get/set the 'creation-date' field # in OpenOffice.org (normally ISO-8601) date format sub creation_date { my $self = shift; return $self->accessor('//meta:creation-date', @_); } #----------------------------------------------------------------------------- # get/set the 'creator' field (i.e. author) sub creator { my $self = shift; return $self->accessor('//dc:creator', @_); } #----------------------------------------------------------------------------- # get/set the 'initial-creator' field (i.e. author) sub initial_creator { my $self = shift; return $self->accessor('//meta:initial-creator', @_); } #----------------------------------------------------------------------------- # get/set the 'date' (i.e. the date of last update) field # in OpenOffice.org (normally ISO-8601) date format sub date { my $self = shift; return $self->accessor('//dc:date', @_); } #----------------------------------------------------------------------------- # get/set the 'language' code (ex : 'fr-FR') of the document sub language { my $self = shift; return $self->accessor('//dc:language', @_); } #----------------------------------------------------------------------------- # get/set the 'editing-cycles' field (i.e. the number of editing sessions) sub editing_cycles { my $self = shift; return $self->accessor('//meta:editing-cycles', @_); } #----------------------------------------------------------------------------- # increment the 'editing-cycles' field sub increment_editing_cycles { my $self = shift; my $v = $self->editing_cycles(); return $self->editing_cycles($v + 1); } #----------------------------------------------------------------------------- # get/set the 'editing-duration' field (i.e. the total elapsed time for all # the editing sessions) in OpenOffice.org (ISO-8601) format sub editing_duration { my $self = shift; return $self->accessor('//meta:editing-duration', @_); } #----------------------------------------------------------------------------- # get/set the 'keywords' list sub _od_keywords # Open Document version { my $self = shift; my @new_kws = @_; my @list = $self->getTextList('//meta:keyword'); my $body = $self->{'body'}; NEW_KWS: foreach my $new_kw (@new_kws) { foreach my $old_kw (@list) { next NEW_KWS if ($old_kw eq $new_kw); } $self->appendElement ( $body, 'meta:keyword', text => $new_kw ); push @list, $new_kw; } return wantarray ? @list : join ", ", @list; } sub _oo_keywords # OpenOffice.org version { my $self = shift; my @new_words = @_; $self->_oo_addKeyword($_) for @new_words; my $base = $self->getElement('//meta:keywords', 0); return undef unless $base; my @list = (); foreach my $element ($self->selectChildElementsByName($base, 'meta:keyword')) { push @list, $self->getText($element); } return wantarray ? @list : join ", ", @list; } sub keywords { my $self = shift; return ($self->{'opendocument'}) ? $self->_od_keywords(@_) : $self->_oo_keywords(@_); } #----------------------------------------------------------------------------- # append a new keyword (if unknown) in the keywords list sub _od_addKeyword # Open Document version { my $self = shift; my $new_kw = shift or return undef; my @list = $self->getTextList('//meta:keyword'); foreach my $old_kw (@list) { return undef if ($new_kw eq $old_kw); } $self->appendElement ( $self->{'body'}, 'meta:keyword', text => $new_kw ); return $new_kw; } sub _oo_addKeyword # OpenOffice.org version { my $self = shift; my $new_word = shift; my $kw_base = $self->getElement('//meta:keywords', 0); if ($kw_base) { foreach my $element ($self->selectChildElementsByName($kw_base, 'meta:keyword')) { my $old_word = $self->getText($element); return undef if ($old_word eq $new_word); } } else { $kw_base = $self->appendElement('//office:meta', 0, 'meta:keywords'); } $self->appendElement($kw_base, 'meta:keyword', text => $new_word); return $new_word; } sub addKeyword { my $self = shift; return $self->{'opendocument'} ? $self->_od_addKeyword(@_) : $self->_oo_addKeyword(@_); } #----------------------------------------------------------------------------- # remove a given keyword (if known) from the keyword list sub _od_removeKeyword # Open Document version { my $self = shift; my $kw = shift; my @list = $self->getElementList('//meta:keyword'); my $count = 0; foreach my $element (@list) { my $old_kw = $self->getText($element); if ($old_kw eq $kw) { $self->removeElement($element); $count++; } } return $count; } sub _oo_removeKeyword # OpenOffice.org version { my $self = shift; my $word = shift; my $kw_base = $self->getElement('//meta:keywords', 0); return undef unless $kw_base; my $count = 0; foreach my $element ($self->selectChildElementsByName($kw_base, 'meta:keyword')) { my $old_word = $self->getText($element); if ($old_word eq $word) { $kw_base->removeChild($element); $count++; } } return $count; } sub removeKeyword { my $self = shift; return $self->{'opendocument'} ? $self->_od_removeKeyword(@_) : $self->_oo_removeKeyword(@_); } #----------------------------------------------------------------------------- # remove the keyword list sub _od_removeKeywords # Open Document version { my $self = shift; my @list = $self->getElementList('//meta:keyword'); my $count = 0; foreach my $element (@list) { $count++; $self->removeElement($element); } return $count; } sub removeKeywords { my $self = shift; return ($self->{'opendocument'}) ? $self->_od_removeKeywords(@_) : $self->removeElement('//meta:keywords', 0); } #----------------------------------------------------------------------------- # get/set the list of the user defined fields of the meta-data # without argument, returns a hash where keys are the field names # and values are the field values # to set/update the user-defined fields, arguments should be passed # as a hash with the same structure sub user_defined { my $self = shift; my %new_fields = @_; my @elements = $self->getElementList('//meta:user-defined'); if (%new_fields) { my $count = 0; foreach my $key (sort keys %new_fields) { my $element = $elements[$count]; last unless $element; $self->setAttribute($element, 'meta:name', $key); $self->setText($element, $new_fields{$key}); $count++; } } my %fields = (); foreach my $element (@elements) { my $name = $self->getAttribute($element, 'meta:name'); my $content = $self->getText($element); $fields{$name} = $content; } return %fields; } #----------------------------------------------------------------------------- sub getUserPropertyElements { my $self = shift; return $self->getElementList('//meta:user-defined'); } #----------------------------------------------------------------------------- sub removeUserProperties { my $self = shift; my $count = 0; foreach my $element ($self->getUserPropertyElements()) { $element->delete; $count++; } return $count; } #----------------------------------------------------------------------------- sub getUserPropertyElement { my $self = shift; my $arg = shift; return undef unless defined $arg; if (ref $arg) { return ($arg->hasTag('meta:user-defined')) ? $arg : undef; } else { my $name = $self->inputTextConversion($arg); return $self->getNodeByXPath ("//meta:user-defined[\@meta:name=\"$name\"]"); } } #----------------------------------------------------------------------------- sub getUserProperty { my $self = shift; my $property = $self->getUserPropertyElement(shift); my $type = undef; my $value = undef; if ($property) { $type = $self->getAttribute($property, 'meta:value-type'); $value = $self->getText($property); } return (wantarray) ? ($type, $value) : $value; } #----------------------------------------------------------------------------- sub setUserProperty { my $self = shift; my $name = shift; unless (defined $name) { return (wantarray) ? (undef, undef) : undef; } my %opt = @_; my $property = $self->getUserPropertyElement($name); unless ($property) { $property =$self->appendElement ($self->{'body'}, 'meta:user-defined'); $self->setAttribute($property, 'meta:name', $name); } my $type = $opt{'type'} || $self->getAttribute ($property, 'meta:value-type') || 'string'; $self->setAttribute($property, 'meta:value-type', $type); $self->setText($property, $opt{'value'}) if defined $opt{'value'}; return $self->getUserProperty($property); } #----------------------------------------------------------------------------- sub removeUserProperty { my $self = shift; my $name = $self->inputTextConversion(shift); return undef unless defined $name; my $property = $self->getNodeByXPath ("//meta:user-defined[\@meta:name=\"$name\"]"); return $self->removeElement($property); } #----------------------------------------------------------------------------- sub statistic { my $self = shift; my %new_fields = @_; my $element = $self->getElement('//meta:document-statistic', 0); unless (%new_fields) { return $self->getAttributes($element); } else { return $self->setAttributes($element, %new_fields); } } #----------------------------------------------------------------------------- sub getTemplate { my $self = shift; my $element = $self->getElement('//meta:template', 0) or return undef; my %t = $self->getAttributes($element); return (wantarray) ? ($t{'xlink:href'}, $t{'meta:date'}, $t{'xlink:title'}) : $t{'xlink:href'}; } #----------------------------------------------------------------------------- sub unlinkTemplate { my $self = shift; return $self->removeElement('//meta:template', 0); } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/OODoc/XPath.pod0000644000175000017500000033463311354565630015334 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::XPath - Low-level navigation in the documents =head1 DESCRIPTION This module is a low-level class which uses OODoc::File (without inheriting anything from it) along with the classes defined in the XML::Twig module. It's a common basis for the other, more user- friendly, document-oriented modules. It uses XPath expressions in order to retrieve any document element (but it doesn't provide a full implementation of the XPath standard). In addition, while the most part of the provided methods are OpenDocument-aware, this module could be used against any other kind of XML documents, simply because it benefits from all the features of XML::Twig. Such a possibility may prove useful for applications that simultaneously process OpenDocument and non-OpenDocument XML files. The OpenOffice::OODoc::XPath class should not be explicitly used in the applications, because all its features are available in more user-friendly classes such as OODoc::Text, OODoc::Styles, OODoc::Image, OODoc::Document and OODoc::Meta. The present manual page is provided to describe the common methods and properties that are available with all these classes. This chapter can be skipped by programmers who are only interested in upper level methods provided by the OODoc::Text, ::Styles, ::Image and ::Meta modules. Understanding these modules is easier and using them requires less Perl and XML expertise. However, calling OODoc::XPath methods remains a good rescue option as it allows all kinds of operations on all types of XML elements contained in any OpenDocument-compliant file. OODoc::XPath is the common foundation of OODoc::Meta, OODoc::Text, OODoc::Styles and OODoc::Image. It contains the lowest layer of navigation services for XML documents and handles the link with OODoc::File for file access. Its primary role is as an interface with the XML::Twig API. In the present manual chapter, you will see "elements" often mentioned. When it says that a module expects a parameter or returns an element (either singly or as a list), it is referring to an XML element. It is important to distinguish elements from their content (elements being simply references to XML data structures). To read or modify the content of an element such as its text or XML attributes, use the accessors also available within OODoc::XPath. In most cases where XPath methods require a reference to an element as an argument, there are two ways of proceeding: - reference the element directly (obtained previously) - or give an XPath expression and a position, being a string and an integer respectively; for example, the pair ('//office:body/text:p', 12) or ('//text:p', 12) represents the thirteenth occurrence of the 'text:p' element, i.e. the 13th paragraph (occurrences are numbered starting from 0). The second way requires the knowledge of an appropriate XPath expression (according the OpenDocument XML format specification). And a given XPath expression is not necessarily the same with an OpenDocument as in an OpenOffice.org document. So you should preferently use high level accessors (provided by derivative classes such as OODoc::Document) and avoid XPath hardcoding. However, you know you can at any time reach any element with XPath. Of course, you will never need to use XPath expressions in order to reach the most common text elements (such as paragraphs), because the OODoc::Text module provides more friendly accessors (for example, you will probably use the getParagraph() method and forget "//text:p"). Some methods accept both forms which means that if the first parameter is recognised as an element reference, the position does not need to be given. Therefore the number of arguments for certain OODoc::XPath methods can vary. For those who really want to access all areas there are also OODoc::XPath methods which allow unrestricted access to every element or XML attribute via an access path in XPath syntax. If you are into this kind of thing, we recommend you obtain good syntax reference manuals for XPath and OpenDocument and a supply of aspirin. Methods which may return several lines of text (e.g. getTextList) do so either in the form of an unique character string containing "\n" separators or in table form. Unless otherwise stated, the word 'document' in this chapter only refers to XML documents contained within OODoc::XPath objects and not, say, OpenDocument files (as an end user would use). Amongst the different methods which return elements, attributes or text, some are called getXxx, others selectXxx or findXxx. Read methods whose names start with "get" generally refer to an unfiltered object or list, whereas others return an object or list filtered according to a parameter value. In this latter case the search parameter is treated as a standard expression and not an exact value. This means that if the search criteria is "xyz", all text containing "xyz" will be considered a match. To restrict the search to text exactly equal to "xyz", use "^xyz$" as the search criteria (following Perl regular expression syntax). Several methods allow you to place copies of or references to elements (from other documents or from other positions in the same document) in any position in the current document. This offers powerful manoeuvrability but only if these placements conform with the destination position's context. For example, you can easily copy a paragraph from one document to another but only if you knowingly modify the paragraph's style attribute if that style is not already defined in the destination document. You can also copy the style but only if you are sure that this style is not already defined by another unknown style in the destination document (and so on). For advanced users familiar with the XML::Twig API, it might be interesting to know that all the objects called "elements" in the following chapters are objects of the OpenOffice::OODoc::Element class, which is an XML::Twig::Elt derivative. So all methods associated with this class are directly applicable to these elements, on top of the functionality described in this manual. However, the knowledge of XML::Twig is not mandatory. Important note: The applications should not explicitly work with this class. We recommend using OODoc::Meta and OODoc::Document (which are both OODoc::XPath derivatives). These two objects provide highest-level methods which are neater and more productive. Explicit use of OODoc::XPath methods (which sometimes require large numbers of parameters) should only be considered as a last resort in unexpected circumstances for access to any element or XML attribute not handled by more friendly methods. However, the present manual chapter could prove helpful because all the common features of OODoc::Meta and OODoc::Document are described here. =head2 Methods =head3 Constructor : OpenOffice::OODoc::XPath->new(); Short Form: odfXPath() Returns a new OpenDocument connector, i.e. an interface which can be used for subsequent operations on a well-formed document. This constructor should not be called directly; it's implicitly triggered each time a Meta or Document object is created. So the following description apply to odfMeta() and odfDocument(). The document is loaded and parsed according to various options. The most used option is 'file'; it simply allows the application to process an OpenDocument file selected by its path/name in the file system. Example: my $doc = odfXPath ( file => "myfile.ods", part => "content" ); # ... lot of processing ... $doc->save; Returns a new document connector. In the example above, the object is loaded from a regular OpenDocument file, that is the most current option, but there are other possibilities. It's possible to use flat XML (available as a string in memory, or loaded from a file). In addition, this constructor is able to create a new document from scratch. The value of the 'file' option may be an open IO::File object, that allows the application to use an application-provided file handle. However, you should prefer file paths/names when possible, and read the explanations about the constructor and the save() method in the OpenOffice::OODoc::File manual page before using open file handles. Remember that, as soon as the given file or handle is an ODF container, OODoc::XPath uses OODoc::File. Parameters are named (hash key => value). The constructor must get at least one parameter giving a means of obtaining the XML document that it will represent. Several options are available; each one is represented through the following examples: # option 1 (using an existing flat XML document) my $doc = odfXPath(xml => $xml_string); # option 2 (using a previously created ODF file interface) my $oofile = odfContainer('source.odt'); my $doc = odfXPath(container => $oofile, part => 'meta'); # option 3 (using a regular ODF file directly) my $doc = odfXPath(file => 'source.odt', part => 'content'); # option 4 (multiple instances against a single file) my $content = odfXPath(file => 'source.odt', part => 'content'); my $meta = odfXPath(file => $content, part => 'meta'); my $styles = odfXPath(file => $content, part => 'styles'); Remember "odfXPath()" represents "OpenOffice::OODoc::XPath->new()" in the instructions above, and you can (and should) use this shortcut provided that you have loaded the main OpenOffice::OODoc module, and not only and explicitly the OpenOffice::OODoc::XPath module. The first form uses an XML string directly (previously loaded or created by the program). To be used for very specific applications working with flat XML documents exports and not with standard OOo/OpenDocument files. The second method links OODoc::XPath to an existing OODoc::File object (through the "container" option) and indicates which XML part it is to extract (metadata, content, styles, etc). The OODoc::File is an abstraction of an already open ODF container. It can be shared, i.e. several OODoc::XPath objects can be instantiated with the same OODoc::File object, and this possibility must be used when several OODoc::XPath objects have to bring consistent changes in a single file (see option 4 below). In order to create the required OODoc::File object, simply use odfFile() with a filename as argument (for advanced use, see OpenOffice::OODoc::File). The third method is the easiest, because the user just provide a filename and a member, and all the file interface is run silently (i.e. an invisible OODoc::File object is automatically created and used to get the content). It's probably the most used approach; its recommended when the user doesn't need to get more than one member in the same file. The 'part' option is a selector that tells what component is needed (content, styles, metadata, ...) knowing that an OODoc::XPath object can handle only one component. Its default value is 'content'. Note that the 'part' option replaces the deprecated 'member' option. However, for compatibility reasons, 'member' is supported yet (if both 'member' and 'part' are erroneously provided, 'member' prevails). If the application needs to process, say, the content and the styles in the same session, it must create two, or more, OODoc::XPath objects possibly associated with the same file interface. The appropriate way is shown in our last example above. The first instance is associated with a filename. Then the other instances are created with the first one, provided as the value of the 'file' option instead of a filename. The constructor tries to be user-friendly: if the 'file' value is a character string, it's regarded as a filename, but if this value, is an existing OpenOffice::OODoc::XPath object, the new object is automatically connected to the same file interface as the other one. The file interface is transparently provided by a common shared OpenOffice::OODoc::File object (you can safely ignore the features of this object, but a corresponding manual chapter is available for more details). Be careful: creating more than one OpenOffice::OODoc::XPath objects linked by their 'file' parameters to the same explicit filename (and not linked with each other) produces useless extra I/O operations and possible conflicts. Caution: being associated with a common interface via OODoc::File, none of these OODoc::XPath objects should be deleted before the final save() call for this archive. So by calling a save, the File object "calls up" all the XPath objects which were "connected" to it in order to "ask" each of them for the changes which were made to the XML (content, styles, meta, etc.). The results are unpredictable if any of them is absent when called. If the provided filename has a ".xml" or ".XML" suffix, or whatever the name if the 'flat_xml' option is set to 1, the file is processed as flat XML and not as a regular OOo file. No OODoc::File object is created, and the result of a subsequent call of the save() method produces a flat XML export (and not a regular OOo/OpenDocument file). You can pass the optional parameter 'element' in any case where the constructor is called without the 'xml' parameter. Bearing in mind that an OODoc::XPath object will not necessarily handle an entire XML document, this extra parameter indicates the name of the XML element to be loaded and handled. If the 'element' parameter is not given for an OpenDocument file, a default element will be chosen according to the following table: 'meta' => 'office:document-meta' 'content' => 'office:document-content' 'styles' => 'office:document-styles' 'settings' => 'office:document-settings' 'manifest' => 'manifest:manifest' Conversely, the 'element' parameter becomes mandatory if the chosen XML element is not listed above. Through OODoc::File, OODoc::XPath can actually access archives which are not necessarily in OpenDocument format and may be, for example, "databases" of presentation and content templates. If the application needs to create a new document, and not process an existing one, an additional option must be passed: create => "" where "class" must be one of the following list: "text", "spreadsheet", "presentation" or "drawing", according to the needed content class. And, for very special needs, the user can pass an additional "template_path" to select an ad hoc directory of XML templates instead of the default one. This user-provided directory must have the same kind of structure and content as the "templates" subdirectory of the OpenOffice::OODoc installation. An additional 'opendocument' option can be provided and set to 'true' or 'false'. If this option is 'false', the new document is created according to the OpenOffice.org 1.0 format instead of the OASIS OpenDocument format. The default format is OpenDocument. The 'opendocument' option works for new documents only and is ignored unless the 'create' option. This module can create and process either OpenOffice.org 1.0 documents or ODF documents but can't directly convert a document from one format to the other one. OODoc::XPath can process ODF documents provided through XML flat files as well as in the compressed (zip) format. The given file is automatically processed as flat XML if either it's name ends by ".xml" or the 'flat_xml' option is set to '1'. When processing a flat XML file, OODoc::XPath doesn't load the OODoc::File zip interface. So, a subsequent call of the save() method can only export the document as flat XML. An optional 'readable_XML' can be passed. If this option is provided and set to 'on' or 'true', the resulting XML will be smartly indented (and, of course, more space-consuming). This feature is intended for debugging purposes and should not be used in production. The 'local_encoding' option can be set with the appropriate value when a particular character set (and not the default one) must be used for a document. A 'read_only' can be provided and set to 'true' in order to prevent the current member from being written back to the physical ODF file when the save() method is called. Other optional parameters can also be passed to the constructor (see Properties below). =head3 appendElement(path, position, name/xml, [options]); =head3 appendElement(element, name/xml, [options]); Adds a new element or existing element to the list of child elements of an existing parent element given first (by [path, position] or by reference). The argument after the position argument can be an XML element name. Example: $content->appendElement ( '//office:body', 0, 'text:p', text => "New text" ); adds a paragraph containing the phrase "New text" to the end of the document body. (Remember that in the case of an OpenDocument text file (Writer), it would be better to use the appendParagraph method of OpenOffice::OODoc::Text as this requires fewer parameters. If the 'text' option is omitted, an empty element is created (in the above example it would be an empty paragraph or line feed). You can pass the 'attributes' option which is a hash whose keys are the XML attribute names and whose values are the XML attribute values. Use of these options depends on the type of document and the type of element and requires knowledge of OpenDocument conventions. Example: $my_style = { 'style:name' => 'P1', 'style:family => 'paragraph' }; $content->appendElement ( '//office:automatic-styles', 0, 'style:style', attributes => $my_style ); creates a new paragraph style called 'P1' in the list of "automatic styles" ("automatic styles" are styles which are not explicitly indicated in the styles list as it appears to the end user). This method lets you add any kind of element into a document, even exotic ones. With the most common OpenDocument objects (e.g. paragraphs), though, it is easier to use the specialist methods contained in other modules. The 'name' argument can be replaced by an existing element in the same OODoc::XPath object or in another. In which case no element is created but the existing element is simply referenced with a new position even though it remains in its old position. Caution: any modification of an element which is referenced several times in one or more documents is made to all references. If you want to add a similar but separate element, you must use replicateElement which produces a new element from the content of an existing one. The 'name' argument can also be replaced by an XML string. This string must correspond to the correct XML description of a UTF-8 encoded OpenDocument element. For example, it could be a string which had been previously exported using the exportXMLElement method of OODoc::XPath, or extracted from an OpenDocument file by some other application. If for any reason you absolutely have to use a non-UTF8 XML string which contains 8-bit characters (accented letters, etc.), you can always convert the string using the encode_text method before passing it to appendElement. Of course, the problem will not arise if you are absolutely sure that the string only contains ASCII (7 bit) characters. XML syntax is checked, but it is up to the user to verify that the element import conforms to OpenDocument XML grammar. The following piece of code produces the same result as the first example: $xml = '' . 'New text' . ''; $content->appendElement ( '//office:body', 0, $xml ); Using this method, after one or more element creations by direct importation of XML strings, it might be useful to call the reorganize method (but not absolutely necessary). =head3 appendBodyElement(element [, options]) Copies an existing element of any type and appends it to the end of the document body. No new element is created. =head3 appendLineBreak(element) Appends a line break to a text element. This method allows the user to create a single text element (ex: a paragraph) including one or more breaks, instead of separate elements. The example below appends a new text in a new line to the end of an existing paragraph: my $p = $doc->getElement('//text:p', 5); $doc->appendLineBreak($p); $doc->extendText($p, 'A new line in the same paragraph'); =head3 appendSpaces(element, length) Appends a sequence of multiple spaces to a text element, knowing that a string containing repeated spaces shouldn't be stored as is in a document (see setText() and spaces() for details about repeated spaces). =head3 appendTabStop(element) Appends a tab stop ("\t") to a text element. =head3 blankSpaces(length) See spaces(). =head3 cloneContent(oodoc_xpath_object) Cancels the entire document contents of the current instance and replaces it with a reference to the contents of another OODoc::XPath object. Example: $doc1 = OpenOffice::OODoc::XPath->new ( file => 'template.ods', member => 'styles' ); $doc2 = OpenOffice::OODoc::XPath->new ( file => 'sheet.ods', member => 'styles' ); $doc2->cloneContent($doc1); $doc2->save; This sequence replaces the styles and page layout of 'sheet.ods' with those of 'template.ods'. The above example could easily have been written without even using OODoc::XPath by acting directly on the files. For example, extract the 'styles.xml' member from 'template.ods' and insert it into 'sheet.ods'. The use of OODoc::XPath and the cloneContent method guarantees that the transferred content corresponds to an OpenDocument document and allows reads/writes to it on the fly. Caution: the "cloned" content is not physically copied. Calling this method references one single physical content in two documents. Any modifications made to the content of either of these two documents applies equally to the other and vice-versa. =head3 contentClass([class name]) Accessor to get or set the class of the document content. If the current member is a document content, returns its class according to the OpenDocument terminology, i.e. one of the following values: "text", "spreadsheet", "presentation", or "drawing". Returns an empty string if the current member is not a document content (if it's, for example, the "meta" or "styles" member). This accessor is read-only. =head3 createSpaces(length) See spaces(). =head3 createElement(name, text) =head3 createElement(xml) Creates a new element without attributes which is not inserted in a document. Example: my $element = $doc->createElement ('my_element', 'its content'); creates a new XML element without attributes and returns its reference. Instead of a name, the first argument can be the full XML description of the element. Example: my $element = $doc->createElement ('My text'); This new element is temporary: it is not linked to any document. It is destined to be used later by another method. The name can contain a namespace prefix which would look like this: 'namespace:name'. In its second form, a well-formed XML string can be supplied as a single argument. The recognition criteria is the presence of the "<" character at the beginning of the argument. See appendElement for comments on the direct insertion of XML. Explicit calls to createElement() should be rare. This method is normally called silently by higher-level methods which are capable of creating an element, inserting it in a document's XML tree and giving it attributes (see appendElement and insertElement). =head3 createFrame(name => frame_name [, options]) Creates an empty frame. A frame is an OpenDocument object which controls a rectangular area where a visible content is displayed. Possible contents for a frame are text boxes or images. This method works is not focused on a particular document class (for example, it works on text documents as well as on presentations), but the visible effects of some options are not always exactly the same. Possible options are: 'name' => unique name The 'name' is an identifier; if provided, it should be unique for the document. 'attachment' => existing container The value of this option, if provided, must be an existing element which can contain a text box according to the OpenDocument rules. Such an object may be, for example, a draw page if the current document class is 'presentation' or 'drawing', or a paragraph if this class is 'text'. 'page' => page number or name The effects of the 'page' option depends on the content class of the current document. If this option is used, it indicates that the frame will be anchored to a page, and the given value is a page number. It does not matter if, when createFrame() is called, this number is beyond the end of the document or not. If the content class of the document is "presentation" (Impress) or "drawing" (Draw), then the page option must be either the visible name or the object reference of an existing draw page. Caution: the 'page' option is ignored if 'attachment' is provided; in the other hand, either 'page' or 'attachment' nust be provided in order to really include the new frame in the document. 'position' => coordinates The coordinates are provided as a string. They go from left to right and top to bottom. Coordinates should be given here in the form of a string "x,y", and the default unit is centimeter. You can choose any other OpenDocument-supported unit instead by attaching the corresponding usual abbreviation, such as "12.5cm, 35mm" which is the same as "125mm, 3.5cm" or "12.5,3.5", etc. The point ("pt") unit is allowed as well. The default coordinates are "0, 0". By default, the coordinates are relative to the anchor point. So, the coordinates are directly page-related if a valid 'page' option is provided only, but if the box is attached to, say, a paragraph, the origin of the coordinates is the beginning of the paragraph. However, the real interpretation of the coordinates depends on the style. With some style definitions, the coordinates may just be ignored (ex: if the style says "the frame is centered", OpenOffice.org will center the frame whatever its stored coordinates). According to other possible style definitions, the coordinates could be counted from the right and/or from the bottom and not from the left/top. 'size' => the size of the box Provided using as a string using the same syntax and units as the position, the 'size' option is strongly recommended knowing that a sizeless frame couldn't be properly displayed. The width comes first in the string. The height is sometimes ignored, according to the style of the frame: by default, the display height of a text box (which is a particular frame) is automatically adjusted to the content. 'style' => style name The 'style' option allows the application to set the frame style. Caution, a text style can't be used as a frame style. A frame style controls the box properties only (border, background, shadow, and so on), and not the content properties. Reusing an existing frame style through this option is generally a good idea. =head3 currentContext([context]) Accessor allowing the application to change the context for some search methods (including getElement()). The default context is the root of the document. By setting the current context to a lower level object, the application can restrain the search to the descendants of this object. In the example below, the getElement() method retrieves a paragraph by order number in a previously selected section, and not in the whole document. my $section = $doc->getElement("//text:section", $s_number); $doc->currentContext($section); my $paragraph = $doc->getElement("//text:p", $p_number); Without argument, simply returns the previous current context. See also resetCurrentContext(). =head3 decode_text(utf8_string) Caution: this method is a non-exported class method. It must be used like this: OpenOffice::OODoc::XPath::decode_text($utf8_string); and not from an OODoc::XPath instance. Decodes a UTF-8 string and returns an 8 bit character translation of it out of the user's character set, as defined by the following variable: $OpenOffice::OODoc::XPath::LOCAL_CHARSET for which the default value is 'iso-8859-1'. See the Perl/Encode manual for the list of supported character sets. OpenDocument uses UTF-8 XML encoding. Explicit calls to this method should be rare. It is used internally by methods which return text extracted from document content (e.g. getText). Warning to contributors: any method which returns text extracted from ODF documents is based on decode_text; so any modification or improvement of the decoding logic should be made there. =head3 encode_text(editable_string) Class method. Encodes "local" character strings (for writing to ODF documents). Example: $string = OpenOffice::OODoc::encode_text($local_string); The local character string is defined by the following global variable: $OpenOffice::OODoc::XPath::LOCAL_CHARSET for which the default value is 'iso-8859-1'. Explicit calls to this method should generally be avoided. It is used internally by methods which insert text or attribute values into documents (e.g. setText). =head3 dispose() Deletes the calling document object. Recommended as soon as the object is no longer needed by the application, and sometimes mandatory to avoid memory leaks, especially in long-running processes. =head3 exportXMLBody() Returns the XML string for use by another application representing the body of a document, without UTF8 decoding. =head3 exportXMLContent() See getXMLContent() =head3 exportXMLElement(path, position) =head3 exportXMLElement(element) Returns the XML string which represents a particular document element (style definition, paragraph, table cell, object, etc.) for use by another application without UTF8 decoding. This method is principally designed to allow remote exchanges of elements between programs using any XML storage or transfer method. It acts as "sender" whilst the "receiver" can use appendElement or insertElement (for example) to insert any exported elements into a document. Example: # sender programme # ... open (EXPORT, "> transfer.xml"); print EXPORT $doc->exportXMLElement('//text:p', 15); close EXPORT; # receiver programme # ... open (IMPORT, "< transfer.xml"); $doc->appendElement('//office:body', 0, ); close (IMPORT); In this example, a paragraph is transferred but it could just as easily be any content, presentation or metadata element. Conversely, this method is not needed when transferring an element from one document to another in the same program (or from one document position to another). An element can be copied directly from within the same program by reference or replication without going via its XML (see appendElement(), insertElement() and replicateElement()). =head3 extendText(path, position, text [, offset]) =head3 extendText(element, text [, offset]) Appends the given text to the previous content of the given element. If the optional 'offset' element is provided, the new element is inserted at the given position. Example: $doc->setText($p, "Initial content"); $doc->extendText($p, " extended"); Assuming $p is a regular text element (ex: a paragraph), its content becomes "Initial content extended". If the second argument is an element itself, it's appended as is to the first element. This feature can be used, for example, in order to append sequences of repeated spaces: $doc->setText($p, "Begin"); $spaces = $doc->spaces(6); $doc->extendText($p, $spaces); $doc->extendText($p, "End"); After the code sequence above, the $p element contains: "Begin End" knowing that a single string containing repeated spaces could not be properly processed by extendText(), even if the 'multiple_spaces' property is set (this property affects the setText() method only). (See also setText()). =head3 findElementList(element, filter [, replacement]) Returns all the children of the given element whose content matches the given filter (regexp). If the third argument ('replacement') is given, every string which matches the filter in each child element will be replaced by this 'replacement' value. This 'replacement' argument can be a character string or a function reference. (See replaceText() method below.) Filtering and possible replacement only affects an element's content and not its attributes. This method is mostly for internal use. We recommend using other methods for the selective extraction of elements. =head3 flatten(element) Converts in place the content of the given element to a flat string, removing any structure. Same as $element->flatten() (see flatten() in the "Element methods" section below). If no element is provided, "flattens" the current context element, which is, by default, the root of the document (be careful !). =head3 getAttribute(path, position, name) =head3 getAttribute(element, attribute_name) Returns the value of a given attribute in a given element. The element is the first argument, the name of the attribute the second one. The return value is undef if the attribute is not defined in the given element. Example: my $element = $doc->getElement('//text:p', 15); my $style = $doc->getAttribute($element, 'text:style-name'); returns the style for paragraph 15. If the given attribute name doesn't include a namespace prefix, the namespace of the attribute is automatically supposed to be the same as the namespace of the element. In addition, any blank space within the attribute name is regarded as a '-'. So, the same example could be be written more concisely as shown below: my $element = $doc->getElement('//text:p', 15); my $style = $doc->getAttribute($element, 'style name'); =head3 getAttributes(path, position) =head3 getAttributes(element) Returns a list of the element's attributes in the form of a hash whose keys are the attributes' XML names. =head3 getBody() Returns the root of the document body. The document body is the main container of all the displayable content not including page headers, page footers, and page backgrounds. =head3 getDescendants(tag [, context]) Returns the list of the descendants of the given context element matching the given tag. Example: my $section = $doc->getSection("SectionName"); my @paragraphs = $doc->getDescendants('text:p', $section); Here, @paragraphs is the list of all the paragraphs which are the descendants (at every level) of a given section (the getSection() method is described in the OpenOffice::OODoc::Text chapter). If the second argument is not provided, the current context of the document is used (see currentContext()). =head3 getElement(path [, position [, context]]) This method is provided in order to allow the user to retrieve any element in any kind of XML document (ODF-compliant or not) using an application-provided XPath expression. It should be used with elements whose type is not explicitly supported by the more focused (and more user-friendly) methods, described in other manual chapters (::Text, ::Styles, ::Meta, and ::Document). This method returns an element's reference. The position argument is used to select a particular element, in the order of the document, knowing that the given XPath expression could select a set of elements. Without it, getElement() returns the first element matching the given XPath. The XPath expression applies in the current context, and not always in the whole document (see currentContext()). However, if the reference of a previously selected element is provided as a third argument, the given element is used as the context. Position indicators start at 0 just like in Perl tables (and some other programming languages). Example: my $p = $doc->getElement('//table:table', 0) indicates an element containing the first table of a text document or first sheet of a spreadsheet. Positions can also be counted backwards from the end by giving negative values, i.e. position -1 being the last element. Thus: my $h = $doc->getElement('//text:h', -2); indicates the second-last header of a text document. Note: None of the two examples above should be used in a real application, knowing that the ::Text module provides getTable() and getHeading() that do the job without XPath coding. When successful, this method ensures that the returned object is indeed an element and not another type of node (e.g. attribute, text, comment, etc.). Such an object is never a printable text; it's either a text container (whose content may be extracted using getText() or getFlatText()) or a non-text element (such as a style, a font declaration, a variable field, a document properties container, etc). Limit: getElement() doesn't implement the full XPath specification, while it supports a large subset (see the XML::Twig documentation for details about the current XPath coverage). =head3 getElementByIdentifier(id [, options]) Returns an element according to the given identifier, if any, or undef otherwise. Note that, according to the ODF 1.1 standard, some elements have identifiers (i.e. text:id attributes), while most haven't, so this method can't work with any object. Allowed options are: tag => restricts the search to a given element tag context => restricts the search to a given context Example: $section = $doc->getElement('//text:section', 0); $note = $doc->getElementByIdentifier( "id004", tag => 'text:note', context => $section ); This sequence selects the note (i.e. footnote or endnote) identified by "id004" if such a note appear in the first section of the document. Without the 'context' option, the search space would be the current context (that is the whole document by default). Without the 'tag' option, the first object that owns the given identifier is selected, whatever its tag. See also getIdentifier(), setIdentifier(), identifier(). =head3 getElementList(path [, context]) Returns a list of all elements at a specified path. Example: my @ref_summary = $doc->getElementList('//text:h'); The above example returns a table containing all header elements of a text document. The path can of course be a more complex XPath expression stipulating, for example, a selection of attribute values. In most cases, you should avoid complicating things unnecessarily (especially in Text, Image and Styles modules), as there are methods for searching by element type, attribute and content which are much easier to use and avoid the need to supply XPath expressions. An optional context argument may be provided in order to restrict the search space. Note: the returned list contains elements in the sense of getElement() and not a list of element contents. =head3 getFirstTextRun(path, position) =hread3 getFirstTextRun(element) Returns the first text segment of an element whose text content is segmented due to one or more child elements. In other words, returns the beginning of the text content up to the first child element, if any. If the given element just contains flat text, without any child element, returns the whole text, just like getText() introduced below. =head3 getFlatText(path, position) =head3 getFlatText(element) Like getText() below, but without rendering of possible tab stops, line breaks, repeated spaces, or any other markup. The returned text is just a decoded flat string. =head3 getFrameElement(name/number) Selects the frame identified by the given name, or by the given order number in the document context. =head3 getIdentifier(path, pos) =head3 getIdentifier(element) Returns the identifier (text:id) of the given element, if any. See also identifier(), setIdentifier(), selectElementByIdentifier(). =head3 getNodeByXPath(xpath_expression) =head3 getNodeByXPath(xpath_expression, context) =head3 getNodeByXPath(context, xpath_expression) A low-level method which returns the node corresponding to the given XPath expression, if it exists in the document. This method (which gives unrestricted access to the entire content of a document) is designed for use with the unexpected. You will obviously need to be familiar with XPath syntax (not documented here) as well as OpenDocument structure. See also selectNodesByXPath(). =head3 getObjectCoordinates(object) Returns the coordinates (X, Y) of the target object, if any. This method makes sense with "positioned" objects, i.e. with frames and frame-like objects (images, text boxes). In an array context, the coordinates are returned as two distinct strings (horizontal, then vertical position). In a scalar context, the values are returned in a single string, and separated by a comma. See createFrameElement() for details about the coordinates and size units and notation. =head3 getObjectDescription(object) Returns the litteral description of a visible object. This method makes sense for frames or frame-like objects (such as images or text boxes). =head3 getObjectName(element) Returns the name of the given element, if any. =head3 getObjectSize(object) Returns the size of the given object, if any. This method works with frames and other frame-based objects, such as images and text boxes. In the returned data, the width comes first, followed by the height. The size is returned in the same way as the coordinates with getObjectCoordinates(). =head3 getPartName() Returns the name of the document part, i.e. 'content', 'styles', 'meta', and so on. =head3 getRoot() Returns the absolute root element of the document. The root element contains any other visible or non visible object, including the document body (see getBody) and style definitions. =head3 getText(path, position) =head3 getText(element) Returns text in the local character set, possibly UTF-8 decoded, contained in the element given as an argument (by path/position or by reference). See also getFlatText(). Two equivalent examples: # version 1 my $element = $doc->getElement('//text:p', 4); my $text = $doc->getText($element); # version 2 my $text = $doc->getText('//text:p', 4); Version 2 is better if the only aim is to get the text from paragraph 4. Version 1 is better, however, if during the course of the program you want to perform other operations on the same paragraph. Giving an element's reference will mean avoiding element handling methods having to recalculate a reference from the XPath path. =head3 getTextList(path) Returns text from all elements in the specified path. Example: my $summary = $doc->getTextList('//text:h'); my $report = $doc->getTextList('//text:span'); The $summary variable contains a concatenation of all headers. $report contains all the words or character strings that "stand out" which the user has designated by their context, e.g. words in italics in a non-italic paragraph. In a list context, the returned data is a table, each of whose elements contains the text of an XML element. In a scalar context (as in our two examples), the returned value is a unique piece of editable text and each element's content is separated from that of the following element by a line feed. =head3 getTextNodes(context [, filter]) Returns the text nodes belonging (at any level) to the given context element. So-called text nodes are low-level text runs, without attributes, that populate text containers such as paragraphs, knowing that a paragraph may contain one or more text nodes. For an example, as soon as a bookmark is put within a pararaph, there is (at least) one text node before the bookmark and another one after the bookmark. The textnodes are returned as a list in the order of the context. Note that a text node is not an element, but that every text node in a regular document is a child of a text element (generally a paragraph, a heading or a text span). So, the node-based parent() method may be used to get the element that contains a given text node. The second argument (optional) specifies a search filter. If it's provided, only the matching text nodes are returned. The example below uses getTextNodes() in order to count the text nodes that contain "foo" and that belong to elements whose style is "bar" in the whole document body (beware, this examples uses methods which are introduced in the OpenOffice::OODoc::Text manual chapter): my $context = $doc->getBody; my @list = (); foreach my $tn ($doc->getTextNodes($context, "foo")) { my $style = $doc->getAttribute ($tn->parent, 'style name'); next unless $style; push @list, $tn if $style eq "bar"; } =head3 getUserField(name [, context]) Returns the element (if defined) representing a user-defined field, and corresponding to the given name. See also userFieldValue(). By default, this method works with the first user field declaration matching the given name in the whole document. However, if the calling object is a 'styles' document part, the search is restricted to a given context (provided through an optional 2nd argument) or to the current context. This feature allows the applications to look for user fields whose declarations are associated to page styles. =head3 getUserFields([context]) Returns the list of the declared user-defined fields. The example below prints the names of all the user-defined fields: foreach my $field ($doc->getUserFields) { print $doc->getObjectName($field); } By default, this method returns all the user fields at the document level. However, if the active document part is 'styles', the search is restricted to a given context (provided through an optional 2nd argument) or to the current context. This feature allows the applications to look for user fields whose declarations are associated to page styles. =head3 getVariable(name) Returns the user-defined variable identified by the given name. [Contribution by Andrew Layton] =head3 getVariableElement() See getVariable(). =head3 getXMLContent([filehandle]) Without argument, returns a document's entire XML content. Exports the entire XML content of the current member to a flat file, if a file handle is provided. Note: the exported data are UTF8-encoded. Example: open my $fh, ">:utf8", "myfile.xml"; $doc->getXMLContent($fh); close $fh; Synonym: exportXMLContent() =head3 getXPathValue(xpath_expression) =head3 getXPathValue(context, xpath_expression) =head3 getXPathValue(xpath_expression, context) A low-level method which allows direct access to the value corresponding to the given XPath expression in a document. Character decoding is handled in the same way as with getText. Example: $expression = '//office:automatic-styles' . '/style:style' . '[@style:style-name="P1"]' . '/@style:parent-style-name'; print $doc->getXPathValue($expression); This sequence displays the name of the parent style of automatic style "P1" (if it exists within the document). Remember that more simple methods in Text and/or Styles modules would indeed produce the same result. The optional element reference "context" can be given as an argument either in first or second place. In this case, the search is limited to the section of the document tree below this given element. The default search area is the entire document. Just as with other methods which require XPath paths, this one is primarily for internal use. It should not be used by the majority of applications. =head3 identifier(path, pos [, value]) =head3 identifier(element [, value]) Gets or sets the identifier of the given element. If the value argument is not provided, does the same as getIdentifier(). If provided, the value argument replaces the previous element identifier or creates it if it was not set. This method can change the identifier, but can't remove it, unlike setIdentifier(). See also getIdentifier(), setIdentifier(), getElementByIdentifier(). =head3 insertElement(path, position, name/xml [, options]) =head3 insertElement(element, name/xml [, options]) Inserts a new element before or after the element specified by [path, position] or by reference. If the "name" argument is a literal, a new element with the name given is created and then inserted. If the same argument is a reference to an existing element, this element is then simply inserted at the position indicated. This method is useful either for adding new elements or for copying elements from one document to another or from one position to another within the same document. The position option allows you to choose the insertion point of the new element. Possible values are "before", "after" and "within" (the default is "before"). If "position" is set to "within", the new element is inserted within the text of the target element, so an additional "offset" option (i.e. a numeric position in the string) is required. However, for insertion within a text container, setChildElement(), described later, is much more powerful. Other options are: text => "text of element" attributes => $attributes The "attributes" option is itself a hash reference containing one or more attributes in the form [name => value] as in appendElement. When successful, this method returns the inserted element's reference (else undef). Example: my $attributes = { 'text:style-name' => 'Heading 2', 'text:level' => '2' }; $doc->insertElement ( '//text:p', 4, 'text:h', position => 'after', text => 'New section', attribute => $attributes ); This sequence (in a text document) inserts a level 2 header 'New section' immediately after paragraph 4. The $name argument can be replaced by an existing element. In this case a new reference to the existing element is inserted, without creating a whole new element. In this way you can display an element at several locations or in several documents which is held in memory only once. See the appendElement section for the consequences of having multiple references to the same physical element. Better to use replicateElement to insert separate copies of an element. In the same conditions as in appendElement, the 'name' argument can be replaced by an XML string which describes the element. Note: to add an element to the end of a document, it would obviously be better to use appendElement(), and to insert an element at a selected position within an existing element, see setChildElement(). =head3 isOpenDocument() Returns 1 (true) if the current document is an OASIS Open Document. To be used every time the application needs to know the format of the document, knowing that some differences between the two formats can't be completely hidden by the API. =head3 lineBreak Returns a special line break element, available for insertion within an existing text element (knowing that "\n" is not recognized as a line break if stored "as is"). The returned element is free, so it could/should be inserted later within a text element. =head3 makeXPath(expression) =head3 makeXPath(context, expression) Low-level method allowing the creation or direct modification without restriction (almost) of any document element. It allows "query" expressions in a language similar to XPath. If the given XPath expression crosses several levels of hierarchy, intermediate nodes can be created or modified "on the fly" by creating the necessary path which in turn creates the final node. Example: $doc->makeXPath ( '//office:body/text:p[4 @text:style-name="Text body"]' ); This "query" applies the "Text body" style to paragraph 4 in the body of the document. (In reality you will probably never use it because the setStyle method of the Text module would do the same thing much more simply.) If, as in the above example, a node is accompanied by a position indicator, it cannot be created but must simply act as a mandatory "passage". This method cannot therefore be used to create, for example, an Nth paragraph if there is already an N-1. The only restrictions apply to namespaces which are given as prefixes to element and attribute names. They must be defined in the document i.e. conform to OpenDocument specifications. For the rest, this method allows the creation of almost anything anywhere within a document. Its use is reserved for OpenDocument XML specialists. In its second form, a context node can be given as the first argument. If present, the path is sought (and if necessary created) starting from its position. By default, the path begins from the root. The returned value is the final node's reference (found or created). The full "query language" syntax used in this method is not documented here. makeXPath is designed to act more as a base for other OpenOffice::OODoc methods than to be used in applications. =head3 moveElements(target_element, element_list) Moves a list of existing elements to a new attachment. One more elements are cut from their previous place and appended as children of the target element. This method can be used to move elements from one place to another place in the same document, as well as from one document to another one (caution, the elements are moved, not copied). =head3 newTextNode(text) Creates a free text node (to be inserted later within a text element). A text node is a piece of flat text, without any attribute, that may be a part or the text content of an element. Note that it's a low level method for special uses; there are various text-oriented methods in the API (mainly described is the ::Text manual page), and the explicit use of text nodes should be avoided. =head3 objectName(element [, name]) Returns the name of the given element. Changes this name is a new name is provided as the 2nd argument. =head3 odfLocaltime() Class method. Converts the numeric time given in argument to an OpenOffice-compliant date (ISO-8601). The argument type is the same as for the standard Perl localtime() function, i.e. a number of seconds since the "epoch". It can be, for example, a value previously returned by a time() call. Without argument, returns the current local time in ISO-8601 format. The result of this function can be used as is in order to set the value of an ODF-compliant date-time element or attribute. =head3 odfTimelocal() Class method. Translates an ODF-formatted date (ISO-8601) into a regular Perl numeric time format, i.e. a number of seconds since the "epoch". So, the returned value can be processed with any Perl date formatting or calculation function. Example: my $date_created = odfTimelocal($meta->creation_date()); $lt = localtime($date_created); $elapsed = time() - $date_created; print "This document has been created $date_created\n"; print "$elapsed seconds ago"; This sequence prints the creation date of a document in local time string format, then prints the number of seconds between the creation date and now. Note that the creation_date() method used here works with the meta-data document part only (see OpenOffice::OODoc::Meta for details about this method). Note: This function requires the Time::Local Perl module. =head3 odfVersion([new_version]) See openDocumentVersion() =head3 ooLocaltime([$time_value]) Class method. See odfLocaltime() =head3 ooTimelocal($oodate) Class method. See odfTimelocal() =head3 openDocumentVersion([new_version]) Returns the version of the Open Document Format (ODF) in use in the current document. If an argument is provided, it's used to set a new version identifier. Beware, this method doesn't really check the conformance of the document to any version of the ODF standard. It just retrieves the value of the version number attribute as it has been set by the application which created or modified the document. If openDocumentVersion() is used to set a new version number declaration, the given value is not checked. So, this value could be the number of a real or future ODF version (1.0, 1.1, 1.2, etc), as well as any other arbitrary value (ex: 99, -1, ...). =head3 raw_import(member, source) Physically imports an external file into an OpenDocument archive associated with an XPath object, if it exists i.e. if the object was created using file or archive parameters. This method only transmits the command to the OODoc::File's raw_import method. Caution: it must not be used with an "active" element i.e. an XML member to which the current XPath object or another XPath object is already associated. Remember too that the import is not actually carried out by OODoc::File until a save and the imported data is therefore not immediately available. =head3 raw_export(member, target) Physically exports a member from an OpenDocument archive associated with an XPath object, if it exists i.e. if the object was created using file or archive parameters. This method only transmits the command to the OODoc::File's raw_import method. =head3 removeAttribute(path, position, attribute) =head3 removeAttribute(element, attribute) Deletes the "attribute" attribute (if found) of the given element by [path, position] or by reference and returns "true". Has no physical effect and returns undef if the attribute has not been defined or if the element does not exist. =head3 removeElement(path, position) =head3 removeElement(element) Deletes the given element (if found) by [path, position] or by reference and returns "true". Returns undef if the element does not exist. =head3 removeIdentifier(path, pos) =head3 removeIdentifier(element) Deletes the identifier attribute ('text:id') of the given element. Be careful, this method should be used in order to delete temporary element identifiers that don't comply with the ODF specification; remember that the identifier is mandatory for some elements. See also getIdentifier(), setIdentifier(), identifier(). =head3 replaceElement(path, position, replacement [, options]) =head3 replaceElement(old_element, new_element [, options]) Deletes the given element by [path, position] or by reference and inserts another element in its place, either from another location in the same document or from another document. A new element can be supplied under the same conditions as for insertElement. By default or by using the mode => 'copy' option, it is a copy of the new element which is inserted. With the mode => 'reference' option, it is only a reference which is inserted. See the section on appendElement for comments on the subject of multiple references to a single physical element. =head3 replicateElement(original_element, position_object [, options]]) Makes a copy of the first given element and inserts it into the current document at a position which depends on the second argument and an optional parameter. If the second argument is an existing object in the document, then the copy is inserted according to an optional 'position' parameter: - if no 'position' option is provided, then the copy is appended as the last child of the position object; - if 'position' => 'before' or 'after', then the copy is inserted at the same hierarchical level as the position object, according to the same logic as for insertElement(). If the second argument is not an object, but simply 'end', then the new element is appended as the very last child of the physical root of the document. See getRoot(). This option should generally be avoided. If the second argument is given as 'body', then the new element is appended at the end of the document body (see getBody), as it was created through appendElement(). Example: my $template = $doc_source->selectElementByAttribute ( '//style::style', 'style:name', 'Text body' ); my $position = $doc_target->getElement ('//office:styles', 0); $doc_target->replicateElement($template, $position); This sequence adds a style 'Text body' to the style set of $doc_target which copies exactly the style of the same name in $doc_source. Obviously, the section of code dealing with the search for the element to copy and its position is the most laborious. (In a real application, thanks to OODoc::Styles, a more user-friendly coding would be allowed for style replication.) This method creates a new element which is an exact copy of the given element, but which is physically separate from it. This method is slower than simply modifying an existing element or inserting an element reference. If the user needs only a "free" copy of the element (out of the document structure, to be later attached), the XML::Twig::Elt copy() method should be preferred: my $new_element = $old_element->copy; =head3 resetCurrentContext() Resets the search context to its default value, which is the root of the document. See currentContext(). =head3 save([filename|filehandle]) Saves the content of the current document through a physical output, that is either a regular file specified by path/name, or an open, application-provided IO::Handle. If no argument is provided, the document which had been used as the source (if any) is used as the default target. Technically, as soon as the document container is a regular ODF file, this method is a stub for the save() method of the associated OpenOffice::OODoc::File object, so all the related explanations and recommendations given in the OpenOffice::OODoc::File manual chapter apply. So, for example, be careful if the target is an open IO::Handle instead of a file path/name. The behaviour of this method depends on the way the current OpenOffice::OODoc::XPath object has been created. If the document is explicitly linked (through the 'file' option of it's constructor) to a regular OOo or OpenDocument file, the document is saved either in the source file, or (if a filename is provided as an argument) in a new file. If the document is linked to the same file interface as one or more other OpenOffice::OODoc::XPath objects, the behaviour is the same as in the previous case, but all the changes made by all the linked objects are automatically saved in the target file. Example: my $content = odfXPath ( file => 'source.odt', part => 'content' ); my $styles = odfXPath ( container => $content, part => 'styles' ) my $meta = odfXPath ( container => $content, part => 'meta' ); # ... a lot of content processing # ... a lot of style processing # ... a lot of metadata processing $content->save('target.odt'); At the end of the sequence above, all the changes made through the $content, $styles and $meta objects are saved in 'target.odt' because these objects share a common file interface. Note that in such a situation, the save() method can be issued from anyone of the objects sharing the file interface (i.e. $content->save could be replaced by $styles->save or $meta->save). However, any XML part (content, styles, meta, ...) whose 'read_only' property is set to "true" is not saved. In the example above, if, say, the $meta object is created (through odfXPath()) with a "read_only" option set to "true", only $content and $styles are really saved by the last instruction. If the document is not associated with a regular OpenDocument compressed file (used through an OODoc::File object), it's saved as "flat XML" to the given file. In such a situation, if the file name is not provided, the source XML file (if any) is used as the target. If the file is "flat XML", OODoc::XPath really effects the physical output, without using any OODoc::File connector. Note: if you need to save a document as flat XML while it's associated with an OpenDocument file, you should use exportXMLContent() with an application-provided file handle. =head3 selectChildElementByName(path, position [, filter]) =head3 selectChildElementByName(element [, filter]) Returns the first (or only) element whose name matches "filter" from within the child elements of the given element indicated by [path, position] or by reference. "filter" is taken to be a regular expression. If several values match the filter, the first of these is returned (in the XML's physical order which is not necessarily the logical order of the document). See the comments about selectElementByAttribute if wanting to select an exact name. Returns undef if no elements match the condition. Returns the first (or only) child (if there are more than one) without anything else if no filter is given or if the filter uses wildcards (".*"). =head3 selectChildElementsByName(path, position [, filter]) =head3 selectChildElementsByName(element [, filter]) Like selectChildElementByName, but returns a list of all elements which match the condition. Example: my @search_words = $doc->selectChildElementsByName ('//text:p', 4, 'text:span'); returns a list of elements from paragraph 4 which correspond to text which has particular attributes which distinguish it from the rest of the paragraph (colour, font, etc.) =head3 selectElements([context,] path, filter) =head3 selectElements([context,] path, filter, replacement) =head3 selectElements([context,] path, filter, action [, arg1, ...]) Returns a list of elements corresponding to a given XPath path and whose text matches the filter (regular expression). The "context" argument, if given, is an element reference which limits the search to its own child elements. The search is carried out in the entire document by default. An element is selected if the search string is found in its own text or in the text of any element descended from it. E.g. An image element (draw:image) can be selected from the value of its attached "description" field. You can replace all strings matching the search criteria with the 'replacement' string, on the fly, if the latter is given as an argument after the filter. Lastly, instead of a replacement string, you can pass a subroutine's reference which will run (in call back mode) each time the search string is matched. If this subroutine returns a defined value, this value is used as the replacement string. The subroutine will automatically receive the rest of the arguments, in this order: Caution: this method can't retrieve a character string which is split into more than one text element or text span. So, for example, it will never retrieve "My String" as long as "My" and "String" are presented with different styles, even if the two parts of the string belong to the same paragraph. If, as is generally the case, you are working exclusively with text elements (paragraphs, headers, etc.), you would be better to use selectElementsByContent() of the Text module which is easier to use and does not require an XPath expression. Here is an example which returns the list of images whose descriptors contain the word "landscape" and displays the name of each selected image: sub printMessage { my $doc = shift; my $element = shift; my $image = $element->parentNode; print "Name: " . $image->find('@draw:name') . "\n"; } my @list = $doc->selectElements ( '//draw:image/svg:desc', 'landscape', \&printMessage, $doc ); Never use this example of code in a real application as it is both purely for demonstration and unnecessarily complex. You can perform the same operation much more simply using the OODoc::Image module. =head3 selectElementByAttribute(path, attribute [, value [, context [, pos]]]) Like selectElementsByAttribute in a scalar context. By default, returns the first element at the given path which has the given attribute containing the given value. If the value is omitted, then returns the first (or only) element that owns the attribute whatever the value. The context optional argument allows to restrict the search space to a given container. The last optional argument, if set, is a positive integer that specifies the index of the required element if more than one element match the other conditions (beware: if the specified position is out of range, the result is undef). The following example (that apply with the "styles" part of an ODF document) prints a message if the "Time New Roman" font face is declared: print "The Time New Roman font is defined !" if $styles->selectElementByAttribute ( 'style:font-face', 'style:name', "Times New Roman" ); Returns undef if no element matches the conditions. See also selectElementsByAttribute(). =head3 selectElementsByAttribute(path, attribute [, value [, context]]) Like selectElementByAttribute(), but for an array context. Returns all the elements that match the path/attribute/value/context conditions as a list. The following example selects a document section whose name is "Foreword" then selects the list of all the level 3 headings in this section (note that $section is used as the optional context argument in the second instruction): my $section = $doc->selectElementByAttribute ('text:section', 'text:name', "Foreword"); my @headings3 = $doc->selectElementsByAttribute ('text:h', 'text:outline-level', 3, $section); (But remember that the same result could be got without knowledge of the XML tags and attributes using more user-friendly methods introduced in other manual chapters !) See also selectElementByAttribute(). =head3 selectFrameElementByName(name) Selects the first frame element whose name is exactly the given argument. A frame is an OpenDocument container which can host a rectangular object, such as an image or a text box. =head3 selectNodesByXPath(xpath_expression) This low-level method returns a list of nodes (which are not necessarily elements) which match the give XPath expression. See getNodeByXPath() for options and comments. =head3 setAttribute(path, position, attribute, value) =head3 setAttribute(element, attribute, value) Modifies or adds an attribute to an element. The element is indicated by reference or by [path, position]. The following arguments are the attribute name and the value. If the name is provided without namespace prefix, it's automatically concatenated to the element's namespace prefix. Every space in the attribute name, if any, is automatically replaced by a '-'. If the value is undef, the corresponding attribute is deleted if it exists in the element; nothing is done otherwise. =head3 setAttributes(path, position, attributes_table) =head3 setAttributes(element, attributes_table) Modifies or adds one or more attributes to an element. The element is indicated by reference or by [path, position]. The list of attributes is given in the form of a hash name => value. Example: my $h = $doc->getElement('//text:h', 12); $doc->setAttributes( $h, 'text:style-name' => 'My Header', 'text:level' => 3 ); This sequence gives the 'My Header' style and level 3 to the 13th "header" element in the document. Any attribute name provided without namespace prefix is automatically concatenated with the namespace prefix of the target element. So, the "text:" prefix could have been omitted in the attribute hash of the example above. In addition, every space in an attribute name is automatically replaced by a '-'. So the code below produces the same result as the previous example: my $h = $doc->getElement('//text:h', 12); $doc->setAttributes( $h, 'style name' => 'My Header', 'level' => 3 ); An attribute provided as undef is deleted, if it exists. =head3 setChildElement(context, tag/element [, options]) Creates a new child element within the text content of an existing one. The context element may be provided like with insertElement(), either by [path, position] or directly as the 1st argument. The next argument is the XML tag of the element to be created, or an existing free element. The given context may be any element, including the whole document body; however, it should be a simple text container in most cases. If the provided tag doesn't include a namespace prefix, it's automatically concatenated with the namespace prefix of the context element (provided as 1st argument). In addition, every space (" ") is regarded as a "-". For example, knowing that the ODF names of a line break and a tab stop are respectively 'text:line-break' and 'text:tab', they may be specified as 'line break' and 'tab' when they are inserted in a regular text paragraph (that is their right place). For alternative and very specific purposes, the tag argument may be replaced by a function reference. If so, the corresponding application- provided function will be triggered with the following arguments: the containing document, a text node, a position, and possibly a string (this last argument will be provided if setChildElement() is called with a 'replace', 'after', 'before' or 'capture' argument introduced below and will contain the matching substring). The application-provided function is supposed to insert one or more contiguous new elements in the text node at the given position (optionally using the given substring); it must return an element. However, most users may safely forget this feature... Allowed options are attributes => attribute/value hash for the new element text => text content for the new element offset => position after => search string (regexp) before => search string (regexp) replace => search string (regexp) capture => search string (regexp) way => search way ('forward' or 'backward') start_mark => element end_mark => element Some of them are mutually exclusive. They work according to the following logic. By default, the new element is created without text and attributes. However, an initial content may be provided through a 'text' optional parameter. In addition, a 'attributes' option allows to provide a set of attributes for the new element as a hash reference; note that every attribute name provided without namespace prefix is automatically concatenated with the same namespace prefix as the given element name. The child element may be inserted at the beginning, at the end, or at a position within the text content. In the last case, the position may be specified by a given numeric argument, or looked for according a given expression. By default, the new element is inserted at the beginning of the target element. An arbitrary other position may be specified with the 'offset' argument, that is either an integer (positive or negative) value, or one of the 'start' and 'end' special indicators. If 'offset' is set to 'start' or 'end', the new element is inserted at the start or at the end, and the other position options are ignored. If 'offset' is a negative integer, the position is counted backward from the end. Caution: if the text of the target container includes tab stops and/or multiple contiguous spaces, the effective offset will be larger than the given one (because ODF tab stops and multiple spaces are special markup elements and not characters). Whatever the value of 'offset', a 'way' option, whose possible values are 'forward' (the default) or 'backward', specifies the search way. If 'offset' is negative, the 'way' option is ignored because the way is always backward. If 'offset' is positive and 'way' is 'backward', then the result is the same as if 'offset' was negative. If 'offset' is 0 or not set and 'way' is 'backward', then the search is done backward from the end. A search string may be provided instead of or in combination with an offset. If so, the insert point will depend on the position of the first substring that matches the given optional search expression (if any). The search expression may be provided through the 'after', 'before', 'replace' or 'capture' option. An expression provided with 'after' or 'before' means that the insert point is immediately after or before the first matching substring. If the search string is provided through 'replace' or 'capture', the matching string will be replaced by the new element. If the option is 'replace' the matching string is just deleted while if 'capture' the same matching string is moved in the new element. Of course, these search string options are mutually exclusive; if more than one of them are wrongly set, only one is considered, and the priority order is 'after', 'before', 'replace', and 'capture'. If both 'capture' and 'text' are set, the result is the same as with 'replace' and 'text'. If the insertion point depends on a search string (i.e. if 'after', 'before', 'replace' or 'capture' is used), it's selected according to the first match. However, it's possible to reverse the search way using the 'way' option. In addition, the search area may be restricted by the 'offset' parameter: if 'offset' is used in combination with any search string option, it specifies the limit of the search area instead of a insertion point; if 'offset' is positive and 'way' is 'forward' (or not set), the search is done from 'offset' to the end; if 'offset' is negative or 'way' is 'backward', the search is done backward from the given offset to the beginning. The 'start_mark' optional parameter is a child element that already exists within the context element. If this parameter is set, it specifies that the search will start from the position of this child element and not from the beginning of the end of the context element. If the search way is forward, the insert point (in case of success) will be located after the start mark, but if the search way is backward the insert point will be before the start mark. And if an offset is provided, it's counted from the position of the start mark. Another existing child element may be used in order to restrict the search area, through a 'end_mark' parameter. If this parameter is set, no search will be done beyond it. If both 'start_mark' and 'end_mark' are provided, the search will run from the first one to the second one Of course, if the start mark is located after the end mark, nothing will be done if the search way is not backward, and vice-versa. The following example inserts a new 'text:time' element (i.e. an ODF time field) immediately after the first "Clock:" substring appearing between the 20th character and the end of a given paragraph (specified by the 1st argument). The new element will be a 'text:time', knowing that the namespace prefix of a paragraph element (text:p) is "text". According to the given attributes, the field will display the current time increased by 15 minutes: $doc->setChildElement( $paragraph, 'time', offset => 20, after => "Clock:", attributes => { 'time-value' => odfLocaltime() } ); The variant below creates a the same 'time' field after each occurrence of "Clock:" (probably not very useful, but the aim is to illustrate the use of 'start_mark' in order to ensure that every field but the first one will be inserted after the previous field): my $field = undef; while ( $field = $doc->setChildElement( $paragraph, 'time', after => "Clock:", start_mark => $field, attributes => { 'time-value' => odfLocaltime() } ) {} Note that the loop body is empty; the start mark, which is undef at the first round, is then the previously inserted child element. Caution: without carefully designed offset and/or search option, such a construct may produce a long or infinite loop (until memory fault); in addition, the setChildElements() method (see below) is generally more appropriate for such repetitive element insertions. The next example creates a text span (i.e. a text area with a special character style) for the last "ODF" substring of a given paragraph: $doc->setChildElement( $paragraph, 'span', capture => "ODF", way => 'backward' attributes => { 'style-name' => "My Style" } ); These examples are shown to illustrate the general logic, not necessarily to be reproduced in real applications, knowing that setChildElement() is a common basis for more specialized methods (mainly introduced in the OODoc::Text man page). See also splitContent(). =head3 setChildElements(context, tag/element [, options]) Like setChildElement() but with a repetitive effect that depends on the options. If 'offset' is the only one option, it's used at a regular interval between insert points. If one of the search string options ('after', 'before', 'capture', or 'replace') is set, 'offset' is used once for all to exclude an area from the search space, and not as an interval between the new elements. The other options work like with setChildElement(). The example below inserts a line break after every ";" in a given paragraph (remember that an ODF line break is an element; it's neither an end of paragraph nor a "\n" character): $doc->setChildElements( $paragraph, 'line break', after => ";" ); =head3 setFlatText(path, position, text) =head3 setFlatText(element, text) Like setText() described below, but without translation of "\t" and "\n". For exceptional use only. Allows, for example, the use of the OODoc API with non-OpenDocument XML files. =head3 setIdentifier(path, pos, value) =head3 setIdentifier(element, value) Sets (or resets) the identifier of the given element. The identifier is namely the 'text:id' attribute, that is allowed for some elements and not for other elements by the ODF standard. OpenOffice::OODoc allows it with any kind of element, and doesn't check its uniqueness, so it may be used with care. A non-conformant element identifier is not an issue if, for example, it's removed before editing or processing the resulting documents through another application. This method removes the identifier if the value argument is undef; however the removeIdentifier() method produces the same result in a more self-documented way). =head3 setObjectCoordinates(object, coordinates) Updates or creates the coordinates (X, Y) attributes of a visible object (ex: image, text box, frame). See createFrameElement() for the coordinates units and notation. =head3 setObjectDescription(object, description) Updates or creates the litteral description of the given object. Should be used for frames, images or text boxes. Caution: the description is not the same as the printable content of a text box. =head3 setObjectName(element, name) Sets or changes the name of the given element according to the given new name. Deletes the name if the given name is undef. =head3 setObjectSize(object, size) Updates or creates the width and height attributes of a given object. This method makes sense for visible, rectangular objects only, such as the frames, images or text boxes. See createFrameElement() for details about the size units and notation. =head3 setRangeMark(type, identifier, parameters) Creates a pair of corresponding delimiting markup elements in place, in order to set up an identified text range (such as a range bookmark, an index mark or a table of content mark). The first argument specifies the type of range; it's mandatory but its value is not checked. Examples of legal types are 'bookmark', 'toc-mark', 'alphabetical-index-mark'. If the provided type doesn't contain a semicolon, it's automatically prefixed according to the content of the 'prefix' parameter (whose default is 'text'). The identifier argument id mandatory; it's an arbitrary (preferently unique) identifier for the pair. While this identifier is generally invisible for the end-user, it's sometimes an explicit name (for example in a range bookmark). The 'prefix' optional parameter allows the applications to specify a particular XML prefix; the default prefix for range marks is 'text'. An arbitrary set of attributes may be provided as a hash through an optional 'attributes' parameter. This hash will be processed according to the same logic as with the common setAttributes() method. The 'context' optional parameter, if provided, specifies the element (which should be a text container, such as a paragraph, a heading or a text span) containing the text range to be delimited. However, if the covered text range is spread across two or more text containers, this parameter must not be set, and a separate 'context' parameter must be provided for the start mark and the end mark (see below). If (and only if) the 'context' parameter is set (meaning that the whole text content between the marks belongs to the same element), a 'content' optional parameter allows to provide an expression; if so, the setRangeMark() will look for the first substring that matches this expression in the target element, and in case of success the range marks will be inserted at the beginning and the end of this substring. The search space of the substring may be restricted using the 'offset' and 'way' parameters, according to the same rules as setChildElement(). Note that the 'replace', 'before' and 'after' parameters don't apply with setRangeMark(). Unless 'context' and 'content' are defined, there are two mandatory parameters, namely 'start' and 'end'; each one is a hash of parameters that apply to the start mark and the end mark, respectively. Each one allows the same options as the option hash of setChildElement(), i.e. 'offset', 'before', 'after', 'replace' and/or 'way' as described above. Note that the 'start' and 'end' structures are ignored as soon as the 'context' and 'content' parameters are set at the first level. In addition, if the start and end marks are not contained in the same text element, separate 'context' parameters must be provided with each one of the 'start' hash and the 'end' hash. However, if the 'end' hash doesn't contain any 'context' parameter, the end mark is supposed to be in the same container as the start mark. The method returns the new start and end marks as a list of elements in array context, or the start mark only in scalar context. In case of failure (due to wrong parameters), both are undef, knowing than setRangeMark() creates the full pair of marks or nothing. Note that the optional attributes (provided through the 'attributes' parameter) are stored in the start mark element only. By default, nothing prevents the applications from creating a range mark whose start point is (temporarily or not) located after the end point, so introducing an inconsistency. However, it's possible to set a 'check' boolean option; if this option is 'true', an order check is done and, if something is wrong, the range mark creation is cancelled and the method fails. On the other hand, as long as the application may ensure that it the start will always be set before the end, the order check should be avoided for performance reasons. Caution: The relative positions of the two marks are not checked, so nothing prevents the users from creating a range whose start point is (temporarily or not) located after the end point in the document. The applications should ensure that the 'start' and 'end' options really specify two locations in the right order. The following instruction creates an index mark covering a text area within a single paragraph (previously selected); the range starts before the "abc" substring and ends after the "xyz" substring; the mark identifier is 'ind1234'. Nothing is done if one of these substrings is not present in the target element: $doc->setRangeMark( 'alphabetical-index-mark', 'ind1234', element => $paragraph, start => { before => "abc" }, end => { after => "xyz" } ); The next example creates a range bookmark (i.e. a bookmark covering a text area) that starts before the "abc" substring in a paragraph and ends at the end of another paragraph: $doc->setRangeMark( 'bookmark', 'bm0001', start => { element => $p1, before => "abc" }, end => { element => $p2, offset => 'end' } ); =head3 setText(path, position, text) =head3 setText(element, text) Uses the given text as the content of the given element. Any previous content (including formatting markup, bookmarks, notes, references, etc) is replaced by the given text. If the given text includes tab stops ("\t") or line breaks ("\n"), they are replaced by the appropriate OpenDocument tags. If this translation must be avoided, use setFlatText() instead. Note: The strings containing repeated whitespaces are not properly processed by default. A sequence of repeated spaces, whatever its length, is replaced by a single space in the target document. So $doc->setText($p, "Begin End"); produces the same visible result as $doc->setText($p, "Begin End"); It's possible to override this default behaviour using the 'multiple_spaces' document property. If 'multiple_spaces' is set to 'on', the repeated spaces in the example above are properly recorded. However, this optional feature is a the price of some other features and, above all, it have a negative impact on the performances (due to an additional processing of *every* space). Of course, a temporary activation of the 'multiple_spaces' feature is allowed, like in the following example, which sets a content including multiples whitespaces: $doc->{'multiple_spaces'} = 'on'; $doc->setText($p, "Begin End"); $doc->{'multiple_spaces'} = undef; See spaces() and extendText() for a workaround if you need to insert repeated spaces without using the 'multiple_spaces' property. =head3 setUserFieldDeclaration(name [, options]) Creates a new user field declaration in the document. The optional parameter are: 'type' => data type (default 'string') 'value' => initial value (default "") 'currency' => a 3-letter currency code (ex: EUR, USD...) See also setTextField() in OpenOffice::OODoc::Text. =head3 spaces(length) Returns a special element, available for insertion within a text element, representing repeated contiguous blank spaces (knowing that repeated spaces can't be properly displayed by an OpenDocument- compliant application if stored as a flat string). The returned element is free, so it could/should be inserted later within a text element. See extendText() for an example of use. =head3 splitContent(path, pos, tag, expression [, attributes]) =head3 splitContent(element, tag, expression [, attributes]) Moves some parts of the text content of the given element and its descendants in new child elements. The tag argument specifies the XML tag of the child elements to be created. Unless this tag is provided with a namespace prefix (or more precisely unless it contains a semicolon), it's automatically concatenated with the namespace prefix of the host element. The following argument is a regular expression that specifies the text substrings to wrap in the new elements. An element is created for every match in the context element and, if any, in its existing children. After these arguments, additional attribute/value pairs may be optionally provided; each one will become an attribute for every created child element (the same name and attributes apply to all). Every attribute name provided without namespace prefix is automatically concatenated to the same namespace prefix as the new elements. This method returns the new child elements as a list. Note that splitContent() is a simplified interface for the mark() method provided by XML::Twig, which may be directly used as an element method for more advanced uses. =head3 splitElement(element, offset) Splits a text element at a given offset. This method is a wrapper of the XML::Twig::Elt split_at() method, so, as said by Michel Rodriguez in his documentation, it splits "a text element in 2" at the given offset so "the original element now holds the first part of the string and a new element holds the right part". In addition, the new element is created with the same attributes (ex: the style or the heading level, if any) as the original one. The new element is inserted immediately after the old one. The method returns both the original and the new elements in a list context. In a scalar context, the new element only is returned. Caution: splitElement() works properly on elements containing "flat text" only. It's a bit complicated to use and probably doesn't produce the right effects on elements containing line breaks, tab stops, "styled spans" or any kind of structure. If it's used with an element containing more that one text segment, it works with the first one only. =head3 tabStop Returns a special tabulation mark element, available for insertion within an existing text element (knowing that "\t" is not recognized as a tab stop if stored "as is"). The returned element is free, so it could/should be inserted later within a text element. =head3 userFieldValue(user_field [, value]) Reads the stored value of a given user field or changes it if a value is provided. The 1st argument can be either the name of the field (as it appears for the end-user) or a previously loaded user field element. See also getUserField(). This method doesn't create any new user field. It can only read or update an existing one. If the given user field is numeric (ex: date, currency) the returned and/or provided value is the internally stored value, and not the displayed one. Warning: the changes made in a document using userFieldValue() don't necessarily produce visible changes for the end-user. This method can update the internal value of a field, but the displayable representations of this field are not automatically refreshed (it depends on a later field update). =head3 variableValue(name/element [, newvalue]) Returns the current value of the given user-defined variable or, if a new value is provided as the second argument, updates the variable accordingly. [Contribution by Andrew Layton] =head2 Element methods Every document element is an OpenOffice::OODoc::Element object, and OpenOffice::OODoc::Element inherits all the rich features of XML::Twig::Elt, including the very powerful copy(), cut(), paste(), move() and replace() methods (look at the XML::Twig documentation for details). Some additional methods, provided in the ::Element package, are described below. The "element methods" should be regarded as reserved for advanced uses, possibly in combination with native XML::Twig::Elt methods (not documented here, but the XML::Twig package itself is well documented). Remember these methods belong to the element and not to the document...! =head3 appendChild(newnode) Appends a node as the last child of the calling node. If the argument is an existing node, it's appended as is. If the argument is a string, a new node is created, with the given string as the XML tag name. =head3 appendTextChild(text) Appends a text node (PCDATA) as the last child of the calling element. =head3 flatten() Converts in place the content of the calling element to a flat string, removing any structure. All the children of the calling element are removed and their text content is concatenated. The resulting string becomes the only content of the element. For example, if the calling element is a table, the tabular structure disappears and is replaced by the concatenated contents of all the cells. Any possible internal tab stop or line break element is removed, as well as any "styled" text span (see setSpan() and removeSpan() is the OODoc::Text chapter for information about styled text spans). Be careful, a lot of elements are not displayed by the OpenDocument compliant software. For example, a section element becomes invisible if it directly contains its text, without structure elements such as paragraphs, headings, tables, and so on. In order to make visible the "flattened" content of a previously complex element, the XML tag should be replaced by the tag of a "displayable" element. In the following example, a section is flattened, then tagged as a paragraph, so its content remains visible: my $s = $doc->getSection("AnySection"); $s->flatten; $s->set_tag('text:p'); Note: getSection() belongs to OpenOffice::OODoc::Text and set_tag() is provided by the underlying XML::Twig::Elt package. The text flattening is sometimes required in order to allow the applications to retrieve strings which are split into more than one text container. For example, a string such as "OpenDocument" can't be retrieved using selectElements() or any other string search method of the API if, say, "Open" and "Office" don't belong to the same text span (i.e. if they have different styles; look at setSpan() in OpenOffice::OODoc::Text to know more about text spans). In such a situation, flatten() removes any text span markup, so the whole text content of the element can be processed as a regular character string. Caution, this method can produce terrific results when misused. =head3 getLocalPosition([regexp]) Returns the position of the current element in the list of all the children of the same parent with the same type. Example: $cell->getLocalPosition(); Assuming $cell is a table cell, this example returns the position of the cell in the row without counting the covered cells (if any). If a regular expression is provided as the optional argument, all the siblings matching the expression are counted; but the method returns zero if the calling element itself doesn't match the expression. Example: $cell->getLocalPosition(qr'table:(covered-|)table-cell'); returns the position of the cell among all the cells (covered or not) in the row. Note: This method is a wrapper of the pos() method of XML::Twig::Elt, but the returned values are zero-based in order to be consistent with the other element addressing features of OpenOffice::OODoc. =head3 insertNewNode(xml_tag, position_flag [, offset]) Creates a new XML element, whose tag is passed as the 1st argument, before, after or within the calling element. The 2nd argument must be set to 'before', 'after', 'within', or any other value accepted by the paste() method of XML::Twig. If the 2nd argument is 'within', a 3rd one must be provided and indicate the offset. =head3 replicateNode(count, position) Produces one or more copies of the calling element and inserts the copies before or after it. The position argument should be 'before' or 'after'; its default is 'after'. Technically, the position argument could be anyone of the position options of the XML::Twig::Elt->paste method, including 'first_child', 'last_child' or 'within'; but any other than 'before' and 'after' probably don't make sense in an OpenDocument-compliant data structure. Without any argument, the calling element is replicated once. But if the count argument is provided and set to zero or a negative value, nothing is done. Example : my $row = $doc->getTableRow("Table1", -1); $row->replicateNode(5); This sequence appends 5 more rows to a table; each new row is a copy of the last original row, including each individual cell and its content. =head3 selectChildElement(filter) Like selectChildElements() below, but returns only the first node matching the filter. Note: the first_child() method of XML::Twig::Elt should be preferred when the filter is the exact tag name of the needed element. =head3 selectChildElements(filter) Selects the children with XML tag names matching a given filter. The filter is processed as a regexp. Note: the children() method of XML::Twig::Elt should be preferred if the filter is the exact tag name of the needed elements. =head3 textLength() Works with text nodes. In array context, returns the length of the text and the text itself; in scalar context, returns the length only. =head2 Properties No class variables are exported; the applications, if needed, must access them using their full name ($OpenOffice::OODoc::XPath:XXX) The following names should be prefixed explicitly with "$OpenOffice::OODoc::XPath::" CHARS_TO_ESCAPE contains the list of reserved characters which, in XML, should be replaced by escape sequences. OO_CHARSET indicates the character set used for OpenDocument document encoding and whose default value is 'utf8' (it should not be changed). LOCAL_CHARSET indicates the user's character set, by default 'iso-8859-1'; it must be changed according to the real user's needs (warning: there is no kind of automatic adaptation to the user's locales, so the application must explicitly load the right value in this variable); it should be done using the odfLocalEncoding() accessor (see the OpenOffice::OODoc man page and, for the list of supported character sets, the Encode module's documentation). The content of these three variables should not normally be directly modified by the applications. Instance hash variables are : 'container' => 'file' => 'part' => 'readable_XML' => <'true' or 'false'> 'local_encoding' => 'multiple_spaces' => <'on' or undef, see setText()> 'element' => 'xpath' => 'twig_options' => 'opendocument' => <'true' or 'false'> However, the 'xml' variable is cleared almost immediately after a successful constructor call, in order to save memory. As soon as the corresponding XPath object has been created, the XML source is no longer required. The 'xpath' variable of an OODoc::XPath object contains a reference to the document structure as it's made available through XML::Twig (see CPAN documentation). This object encompasses the entire current XML tree. Each access to XML using OODoc::XPath objects is done via XML::Twig. So, after having run the following command: my $xp = $doc->{'xpath'}; the experienced programmer will be able to use $xp to access all the functionality of the XML::Twig API, bearing in mind that all operations using this interface will have a direct effect on the content of the $doc object. 'twig_options' allows the user to provide a hash reference of additional options to XML::Twig. These options can modify the way the document is parsed during the execution of odfXPath(). For special applications only (see the XML::Twig reference manual). The 'opendocument' property, if true, means that the document is declared as an OASIS Open Document. If this property is false or undef, the document format is OpenOffice.org version 1. This property should not be changed (as long as OpenOffice::OODoc can't change the format of an existing document). =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2010 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/File.pm0000644000175000017500000004543311415135753015013 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : File.pm 2.203 2010-07-07 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2010 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- package OpenOffice::OODoc::File; use 5.008_000; use strict; our $VERSION = '2.203'; use Archive::Zip 1.18 qw ( :DEFAULT :CONSTANTS :ERROR_CODES ); use File::Temp; #----------------------------------------------------------------------------- # some defaults our $DEFAULT_OFFICE_FORMAT = 2; # OpenDocument format our $DEFAULT_COMPRESSION_METHOD = COMPRESSION_DEFLATED; our $DEFAULT_COMPRESSION_LEVEL = COMPRESSION_LEVEL_BEST_COMPRESSION; our $DEFAULT_EXPORT_PATH = './'; our $WORKING_DIRECTORY = '.'; our $TEMPLATE_PATH = ''; our $MIMETYPE_BASE = 'application/vnd.oasis.opendocument.'; our %OOTYPE = ( text => 'writer', spreadsheet => 'calc', presentation => 'impress', drawing => 'draw', ); our %ODFTYPE = ( text => 'text', spreadsheet => 'spreadsheet', presentation => 'presentation', drawing => 'graphics' ); our %ODF_SUFFIX = ( text => 'odt', spreadsheet => 'ods', presentation => 'odp', drawing => 'odg' ); our %OOO_SUFFIX = ( text => 'sxw', spreadsheet => 'sxc', presentation => 'sxi', drawing => 'sxd' ); #----------------------------------------------------------------------------- # returns the mimetype string according to a document class sub mime_type { my $class = shift; return undef unless ($class && $ODFTYPE{$class}); return $MIMETYPE_BASE . $ODFTYPE{$class}; } #----------------------------------------------------------------------------- # get/set the path for XML templates sub templatePath { my $newpath = shift; $TEMPLATE_PATH = $newpath if defined $newpath; return $TEMPLATE_PATH; } #----------------------------------------------------------------------------- # member storage sub store_member { my $zipfile = shift; my %opt = ( compress => 1, @_ ); unless ($opt{'member'}) { warn "[" . __PACKAGE__ . "::store_member] " . "Missing member name\n"; return undef; } my $m = undef; if ($opt{'string'}) { $m = $zipfile->addString($opt{'string'}, $opt{'member'}); } elsif ($opt{'file'}) { my $f = $opt{'file'}; unless (-r $f && (-f $f || -d $f )) { warn "[" . __PACKAGE__ . "::store_member] " . "Resource $f not available\n"; return undef; } $m = $zipfile->addFileOrDirectory($f, $opt{'member'}); } else { warn "[" . __PACKAGE__ . "::store_member] " . "Missing content to store\n"; return undef; } unless ($m) { warn "[" . __PACKAGE__ . "::store_member] " . "Member storage failure\n[" . $opt{'file'} . "]\n"; return undef; } unless ($opt{'compress'}) { $m->desiredCompressionMethod(COMPRESSION_STORED); } else { $m->desiredCompressionMethod($DEFAULT_COMPRESSION_METHOD); $m->desiredCompressionLevel($DEFAULT_COMPRESSION_LEVEL); } return $m; } #----------------------------------------------------------------------------- # new container creation from template sub _load_template_file { my %opt = ( template_path => $TEMPLATE_PATH, @_ ); my $basepath = undef; if ($opt{'template_path'}) { $basepath = $opt{'template_path'}; } else { require File::Basename; $basepath = File::Basename::dirname ($INC{"OpenOffice/OODoc/File.pm"}) . '/templates/'; } $basepath =~ s/\\/\//g; my $suffix = $opt{'opendocument'} ? $ODF_SUFFIX{$opt{'class'}} : $OOO_SUFFIX{$opt{'class'}}; delete $opt{'class'}; my $source_file = $basepath . '/template.' . $suffix; my $archive = Archive::Zip->new; if ($archive->read($source_file) != AZ_OK) { $archive = undef; } return $archive; } #----------------------------------------------------------------------------- # existing ZIP file container loader sub _load_container { my $container = shift or return undef; my $source = shift or return undef; my $z = Archive::Zip->new; if (UNIVERSAL::isa($source, 'IO::File')) { if ($z->readFromFileHandle($source) != AZ_OK) { warn "[" . __PACKAGE__ . "::new] Handle read error\n"; return undef; } } else { unless ( -e $source && -f $source && -r $source ) { warn "[" . __PACKAGE__ . "::new] " . "File $source unavailable\n"; return undef; } if ($z->read($source) != AZ_OK) { warn "[" . __PACKAGE__ . "::new] File read error\n"; return undef; } } $container->{'source_file'} = $source; return $z; } #----------------------------------------------------------------------------- # control & conversion of XML component names of the OO file sub CtrlMemberName { my $self = shift; my $member = shift; my $m = lc $member; foreach my $n ('content', 'meta', 'styles', 'settings') { if ($m eq $n) { $member = $n . '.xml'; last; } } foreach $m ( @{ $self->{'members'} } ) { return $member if ($member eq $m); } return undef; } #----------------------------------------------------------------------------- # check working directory sub checkWorkingDirectory { my $path = shift || $WORKING_DIRECTORY; if (-d $path) { if (-w $path) { return 1; } else { warn "[" . __PACKAGE__ . "] " . "No write permission in $path\n"; } } else { warn "[" . __PACKAGE__ . "] " . "$path is not a directory\n"; } return undef; } #----------------------------------------------------------------------------- # unique temporary file name generation sub new_temp_file_name { my $self = shift; return File::Temp::mktemp($self->{'work_dir'} . '/oo_XXXXX'); } #----------------------------------------------------------------------------- # temporary data storage sub store_temp_file { my $self = shift; my $data = shift; my $tmpfile = $self->new_temp_file_name; unless (open FH, '>:utf8', $tmpfile) { warn "[" . __PACKAGE__ . "::store_temp_file] " . "Unable to create temporary file $tmpfile\n"; return undef; } unless (print FH $data) { warn "[" . __PACKAGE__ . "::store_temp_file] " . "Write error in temporary file $tmpfile\n"; return undef; } unless (close FH) { warn "[" . __PACKAGE__ . "::store_temp_file] " . "Unknown error in temporary file $tmpfile\n"; return undef; } push @{$self->{'temporary_files'}}, $tmpfile; return $tmpfile; } #----------------------------------------------------------------------------- # temporary member extraction sub extract_temp_file { my $self = shift; my $member = shift; my $m = ref $member ? $member : $self->{'archive'}->memberNamed($member); my $tmpfile = $self->new_temp_file_name; my $result = $m->extractToFileNamed($tmpfile); if ($result == AZ_OK) { push @{$self->{'temporary_files'}}, $tmpfile; return $tmpfile; } else { return undef; } } #----------------------------------------------------------------------------- # temporary storage cleanup # returns the number of deleted files and clears the list of temp files sub remove_temp_files { my $self = shift; my $count = 0; while (@{$self->{'temporary_files'}}) { my $tmpfile = shift @{$self->{'temporary_files'}}; my $r = undef; unless ( -d $tmpfile ) { $r = unlink $tmpfile; } else { $r = rmdir $tmpfile; } unless ($r > 0) { warn "[" . __PACKAGE__ . "::remove_temp_files] " . "Temporary file $tmpfile can't be removed\n"; } else { $count++; } } return $count; } #----------------------------------------------------------------------------- # constructor sub new { my $caller = shift; my $class = ref($caller) || $caller; my $sourcefile = shift; my $self = { 'linked' => [], 'work_dir' => $OpenOffice::OODoc::File::WORKING_DIRECTORY, 'template_path' => $OpenOffice::OODoc::File::TEMPLATE_PATH, 'temporary_files' => [], 'raw_members' => [], 'to_be_deleted' => [], @_ }; my $od = lc $self->{'opendocument'}; unless ($od) { if ($OpenOffice::OODoc::File::DEFAULT_OFFICE_FORMAT == 2) { $self->{'opendocument'} = 1; } } elsif (($od eq '1') || ($od eq 'on') || ($od eq 'true')) { $self->{'opendocument'} = 1; } elsif (($od eq '0') || ($od eq 'off') || ($od eq 'false')) { delete $self->{'opendocument'}; } else { warn "[" . __PACKAGE__ . "::new] Wrong 'opendocument' option\n"; return undef; } if ($self->{'create'}) # new ODF container { $self->{'create'} = 'drawing' if $self->{'create'} eq 'graphics'; $self->{'archive'} = _load_template_file ( class => $self->{'create'}, template_path => $self->{'template_path'}, opendocument => $self->{'opendocument'} ); unless ($self->{'archive'} && ref $self->{'archive'}) { delete $self->{'archive'}; warn "[" . __PACKAGE__ . "::new] " . "Bad or missing template\n"; return undef; } $self->{'source_file'} = $sourcefile || ""; } elsif ($sourcefile) # existing container { $self->{'archive'} = _load_container($self, $sourcefile); return undef unless $self->{'archive'}; } else { warn "[" . __PACKAGE__ . "::new] Missing source file\n"; return undef; } $self->{'members'} = [ $self->{'archive'}->memberNames ]; return bless $self, $class; } #----------------------------------------------------------------------------- # individual zip XML member extraction/uncompression sub extract { my $self = shift; my $member = $self->CtrlMemberName(shift); unless ($member) { warn "[" . __PACKAGE__ . "::extract] Unknown member\n"; return undef; } unless ($self->{'archive'}) { warn "[" . __PACKAGE__ . "::extract] No archive\n"; return undef; } return $self->{'archive'}->contents($member); } #----------------------------------------------------------------------------- # individual zip member raw export (see Archive::Zip::extractMember) sub raw_export { my $self = shift; unless ($self->{'archive'}) { warn "[" . __PACKAGE__ . "::raw_export] No archive\n"; return undef; } my $source = shift; my $target = shift; if (defined $target) { unless ($target =~ /\//) { $target = $DEFAULT_EXPORT_PATH . $target; } unshift @_, $target; } unshift @_, $source; if ($self->{'archive'}->extractMember(@_) == AZ_OK) { return $target ? $target : $source; } else { warn "[" . __PACKAGE__ . "::raw_export] File output error\n"; return undef; } } #----------------------------------------------------------------------------- # individual zip member raw import # file to be imported is only registered here; real import by save() sub raw_import { my $self = shift; my $membername = shift; my $filename = shift; $filename = $membername unless $filename; my %new_member = ('file' => $filename, 'member' => $membername); push @{$self->{'raw_members'}}, \%new_member; return %new_member; } #----------------------------------------------------------------------------- # individual zip member removing (real deletion committed by save) # WARNING: removing a member doesn't automatically update "manifest.xml" sub raw_delete { my $self = shift; my $member = $self->CtrlMemberName(shift) or return undef; my $mbcount = scalar @{$self->{'members'}}; for (my $i = 0 ; $i < $mbcount ; $i++) { if ($self->{'members'}[$i] eq $member) { splice(@{$self->{'members'}}, $i, 1); push @{$self->{'to_be_deleted'}}, $member; return 1; } } return undef; } #----------------------------------------------------------------------------- # archive list sub getMemberList { my $self = shift; return @{$self->{'members'}}; } #----------------------------------------------------------------------------- # connects the current OODoc::File instance to a client OODoc::XPath object # and extracts the corresponding XML member (to be transparently invoked # by the constructor of OODoc::XPath when activated with a 'file' parameter) sub link { my $self = shift; my $ooobject = shift; push @{$self->{'linked'}}, $ooobject; return $self->extract($ooobject->{'part'}); } #----------------------------------------------------------------------------- # copy an individual member from the current OODoc::File instance($self) # to an external Archive::Zip object ($archive), using a temporary flat file sub copyMember { my $self = shift; my $archive = shift; my $member = shift; my $m = $self->{'archive'}->memberNamed($member); unless ($m) { warn "[" . __PACKAGE__ . "::copyMember] Unknown source member\n"; return undef; } my $tmpfile = $self->extract_temp_file($m); unless ($tmpfile) { warn "[" . __PACKAGE__ . "::copyMember] File extraction error\n"; return undef; } my $compress = (($member eq 'meta.xml') || ($member eq 'mimetype')) ? 0 : 1; store_member ( $archive, member => $member, file => $tmpfile, compress => $compress ); } #----------------------------------------------------------------------------- # inserts $data as a new member in an external Archive::Zip object sub addNewMember { my $self = shift; my ($archive, $member, $data) = @_; unless ($archive && $member && $data) { warn "[" . __PACKAGE__ . "::addNewMember] Missing argument(s)\n"; return undef; } # temporary file creation -------------------- my $tmpfile = $self->store_temp_file($data); unless ($tmpfile) { warn "[" . __PACKAGE__ . "::addNewMember] " . "Temporary file error\n"; return undef; } # member insertion/compression --------------- my $compress = (($member eq 'meta.xml') || ($member eq 'mimetype')) ? 0 : 1; return store_member ( $archive, member => $member, file => $tmpfile, compress => $compress ); } #----------------------------------------------------------------------------- # update mimetype sub change_mimetype { my $self = shift; my $class = shift; my $mimetype = mime_type($class); return undef unless $mimetype; my $tmpfile = $self->store_temp_file($mimetype); $self->raw_delete('mimetype'); $self->raw_import('mimetype', $tmpfile); return 1; } #----------------------------------------------------------------------------- # creates a new OO file, copying unchanged members & updating # modified ones (by default, the new OO file replaces the old one) sub save { my $self = shift; my $targetfile = shift; unless ( OpenOffice::OODoc::File::checkWorkingDirectory ($self->{'work_dir'}) ) { warn "[" . __PACKAGE__ . "::save] " . "Write operation not allowed - " . "Working directory missing or non writable\n"; return undef; } my %newmembers = (); foreach my $nm (@{$self->{'linked'}}) { my $ro = $nm->{'read_only'}; next if $ro && (($ro eq '1') || ($ro eq 'on') || ($ro eq 'true')); $newmembers{$nm->{'part'}} = $nm->getXMLContent; } my $outfile = undef; my $tmpfile = undef; # target file check -------------------------- $targetfile = $self->{'source_file'} unless $targetfile; if (UNIVERSAL::isa($targetfile, 'IO::File')) { $outfile = $self->new_temp_file_name(); } else { if ( -e $targetfile ) { unless ( -f $targetfile ) { warn "[" . __PACKAGE__ . "::save] " . "$targetfile is not a regular file\n"; return undef; } unless ( -w $targetfile ) { warn "[" . __PACKAGE__ . "::save " . "$targetfile is read only\n"; return undef; } } $outfile = ($targetfile eq $self->{'source_file'}) ? $self->new_temp_file_name() : $targetfile; } # discriminate replaced/added members -------- my %replacedmembertable = (); my @addedmemberlist = (); foreach my $nmn (keys %newmembers) { my $tmn = $self->CtrlMemberName($nmn); if ($tmn) { $replacedmembertable{$tmn} = $nmn; } else { push @addedmemberlist, $nmn; } } # target archive operation ------------------- # output to temporary archive $self->{'archive'}->writeToFileNamed($outfile); # reload temporary archive my $archive = Archive::Zip->new; my $status = $archive->read($outfile); unless ($status == AZ_OK) { warn "[" . __PACKAGE__ . "::save] Archive I/O error\n"; return undef; } foreach my $oldmember (@{$self->{'members'}}) { my $k = $replacedmembertable{$oldmember}; if ($k) # (replaced member) { $archive->removeMember($oldmember); $self->addNewMember ($archive, $oldmember, $newmembers{$k}); } } foreach my $name (@addedmemberlist) # (added member) { $self->addNewMember($archive, $name, $newmembers{$name}); } foreach my $raw_member (@{$self->{'raw_members'}}) # optional raw data { $archive->removeMember($raw_member->{'member'}); store_member ( $archive, member => $raw_member->{'member'}, file => $raw_member->{'file'}, compress => ($raw_member->{'member'} eq 'mimetype') ? 0 : 1 ) } foreach my $member_to_be_deleted (@{$self->{'to_be_deleted'}}) { $archive->removeMember($member_to_be_deleted); } $status = $archive->overwrite(); # post write control & cleanup --------------- if ($status == AZ_OK) { unless ($outfile eq $targetfile) { require File::Copy; if (UNIVERSAL::isa($targetfile, 'IO::File')) { File::Copy::cp($outfile, $targetfile); unlink $outfile; } else { unlink $targetfile; File::Copy::move($outfile, $targetfile); } } $self->remove_temp_files; return 1; } else { warn "[" . __PACKAGE__ . "::save] Archive write error\n"; return undef; } } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/OODoc/Intro.pod0000644000175000017500000016643511345413766015410 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::Intro - Introduction to the Open OpenDocument Connector =head1 DESCRIPTION This introductory notice is intended to allow the user to understand the general principles and to learn some basic features of the OODoc module without browsing the whole reference manual. The reference manual is a set of OpenOffice::OODoc::xxx separate documents, where xxx is the codename of a particular functional area. The present introduction, as well as the OpenOffice::OODoc main chapter, should be read in order to get the big picture before any attempt to dig in the detailed documentation. Just before reading this intro, it's a good idea to have a look at the short (and commented) examples provided in the distribution. Another general introduction to this Perl OpenDocument Connector has been published in The Perl Review (issue #3.1, dec. 2006) L There is an alternative intro for french-reading users. It's available in ODT (L) or PDF (L). In addition, a general presentation in French can be downloaded at L =head1 Overview The main goal of the Open OpenDocument Connector (OODoc) is to allow quick application development in 2 areas: - replacement of old-style, proprietary, client-based macros for intensive and non-interactive document processing; - direct read/write operations by enterprise software on office documents, and/or document-driven applications. OODoc provides an abstraction of the document objects and isolates the programmer from low level XML navigation, UTF8 encoding and file compression details. For example: use OpenOffice::OODoc; my $document = odfDocument(file => 'filename.odt'); $document->appendParagraph ( text => 'Some new text', style => 'Text body' ); $document->appendTable("My Table", 6, 4); $document->cellValue("My Table", 2, 1, "New value"); $document->save; The script above appends a new paragraph, with given text and style, and a table with 6 lines and 4 columns, to an existing document, then inserts a value at a given position in the table. It takes much less time than the opening of the document with your favourite text processor, and can be executed without any desktop software connection. A program using this library can run without any OpenOffice.org installation (and, practically, OODoc has been tested on platforms where OpenOffice.org is not available yet). More generally, OpenOffice::OODoc provides a lot of methods (probably most of them are not useful for you) allowing create/search/update/delete operations with document elements such as: - ordinary text containers (paragraphs, headings, item lists); - tables and cells; - user fields; - sections; - images; - styles; - bookmarks; - bibliography entries; - page layout; - metadata (i.e. title, subject, and other general properties). Every document processing begins by the initialization of an object abstraction of the document. The most usual constructor for this object is the odfDocument() function. When an object is initialized using this function, it brings a lot of methods allowing allowing the application to retrieve, read, update, delete or create almost every content and style element. Another constructor, odfMeta() is available in order to allow metadata processing (see below). These odfXxx() methods (and others) are shortcuts for OpenOffice::OODoc::Xxx->new(options) where "Xxx" is generally "Document", for full access to the content, but may be another specialized object such as "Manifest" or "Meta". The long "OpenOffice::OODoc::...->new()" syntax can (and should) be avoided, and replaced by the odfDocument(), odfMeta() or odfManitest() functions. A document object initialization requires one or more options. The most usual option is the file name, as in the first example. By default, this parameter is regarded as a previously existing file. It's possible to instantiate a document object with a new, empty document, with an additional "create" option giving the content class of the document to be generated. So, in our first example, the constructor could be: my $document = odfDocument ( file => 'filename.odt', create => 'text' ); This instruction creates a new file containing a text (i.e. an OpenDocument Text) document (and replaces any previously existing file with the same name). However, the new file will be actually created by the $document->save instruction, not by the object initialization. If "create" is set, the documents are generated according to ODF templates. By default, OODoc uses a set of templates which are included in the CPAN package, but it's possible to use custom templates instead. When the 'create' option is in use, the newly created document may be formatted either in the OASIS OpenDocument format (ODF) or in the primary OpenOffice.org 1.0 format. If an additional 'opendocument' is provided and set to 'true', then the new document will be ODF-compliant. If the same option is present and set to 'false', the old OOo 1.0 format will be selected instead. Without the 'opendocument' option, the format will depend on the installation default (in the CPAN distribution, the default is set to OpenDocument but it can be changed by the user at the install time). In the other hand, the provided filename is not used by OODoc in order to select the file format, so you are free to create an ODF file with an OOo-like ".sxw" extension, and so on. The only one filename suffix that is meaningful for OODoc is ".xml" (by default, a file whose name is like "*.xml" is processed as flat XML and not as a regular, compressed ODF file). For existing files, the format (ODF or OOo) is automatically detected according to the real content of the file (whatever the filename). The present version of OpenOffice::OODoc is based on the OpenDocument specification, which has been published (May 2005) as an OASIS standard under the following title: "Open Document Format for Office Applications (OpenDocument) v1.0" It works with ODF 1.1 and 1.2 documents as well, knowing that these newer versions use the same basic data structure as 1.0, and (hopefully) this library doesn't depend on any particular feature which could be removed from the specification. =head1 Architecture The OODoc toolbox is organized in 3 logical layers. It's not necessary for you to remember the (annoying) details given in the next few paragraphs, but these details are described only to explain the general organisation of the modules. If you have only a few dozens of seconds for reading this document, please jump directly at the part III (practical examples) and come back later if you want to know more. =head2 OpenDocument packaging The first layer consists of the OpenOffice::OODoc::File class (defined in the File.pm module). This class is responsible of read/write operations with the ODF physical files. It does every I/O and compression/uncompression processing. It's mainly an easy-to-use, OpenDocument-oriented wrapper for the standard Archive::Zip Perl module (but it could be extended to encapsulate any other physical storage method for the ODF documents). Every physical access to a document through the OpenOffice::OODoc API requires the use of one or more "connectors", each one being associated to an ODF "container". The appropriate constructor is the odfContainer() function, which requires a file name/path as its first (and mandatory) argument: my $resource = odfContainer("myfile.odt"); The instruction above creates an instance of ODF container, associated to a given filename. The returned object (assuming the specified file exists and is readable) is an OpenOffice::OODoc::File instance, i.e. an abstraction of the ODF physical file. However, it's possible to associate a container with an ODF that doesn't exist yet, provided that an additional 'create' named parameter, whose value is the class of the new document, is set. The following example creates an instance of spreadsheet ODF package: my $container = odfContainer("accounts.ods", create => 'spreadsheet'); Note that no persistent resource is created at this time. Without the 'create' option, the odfContainer() function attempts to load the structure of the specified ODF file (and fails if something is wrong). With the 'create' option, the structure is loaded in memory according to defaut ODF templates that belong to the OpenOffice::OODoc installation. But any persistent change (including the creation of the new ODF file, if any) requires the save() method. As an example, the following code really created a new ODF presentation file (without content): my $container = odfContainer("show.odp", create => 'presentation'); $container->save; Or, more concisely: odfContainer("show.odp", create => 'presentation')->save; So, the most minimalistic OpenOffice::OODoc application is a one-liner that creates an empty document. For an existing resource, an open IO::File is allowed instead of a file name. Once initialized, such a container is typically used as a basis to instantiate one or more document-oriented connectors using odfDocument(), introduced later. However, for the users who know exactly what they do, an ODF container brings some low-level methods, such as physical export and/or import of document parts. The next example exports all the named persistent styles of "doc1.odt" then imports them in "doc2.odt": my $p1 = odfContainer("doc1.odt"); my $p2 = odfContainer("doc2.odt"); $p1->raw_export("styles.xml"); $p2->raw_import("styles.xml"); $p2->save; Caution: there is no consistency check with raw_import(), so the application may ensure that the imported part makes sense according to the remainder of the target container (so, in this example, it may ensure that all the styles needed in the document content are conveniently defined in the imported part). Note that the raw_import() method doesn't produce any persistent effect before the save() method is issued from the importing container. All the changes are lost if the program ends or the objects goes out of scope before save(). =head2 XML access An OpenOffice::OODoc::File object which has been instantiated using odfContainer(), it becomes available for processing through document-oriented, XML-based connectors. A typical OpenOffice::OODoc user doesn't need to be really "XML-aware", and most applications will probably use the high-level, XML-free methods provided by the Document and Meta objects, introduced later. However, the present section could prove useful for the general knowledge of the API. The second layer is made of the OpenOffice::OODoc::XPath class (XPath.pm), which is an ODF/XML-aware class. This class is generally not directly used by the applications; it's mainly a common ancestor for more specialised (and more user-friendly) other classes. OpenOffice::OODoc::XPath is an object-oriented representation of an XML part of an OpenDocument file (ex: content.xml, meta.xml, styles.xml, etc.), using the XML::Twig Perl API to access individual XML elements. It provides an XPath-based syntax for advanced users who want to directly get or set any element or attribute in any part of a document. If you want to deal in the same time with several XML components of the same document, you can/must create several OpenOffice::OODoc::XPath against the document (ex: one OpenOffice::OODoc::XPath will be associated with 'meta.xml' to represent the metadata, another one will be associated with 'content.xml' to give access to the content. OpenOffice::OODoc::XPath accepts and provides only XML strings from/to the application; but it's able to connect with an OpenOffice::OODoc::File object for file I/O operation, so you can use it without explicit file management coding. For an example, if you want to get access to the content of any ODF file (say 'foo.odt'), you have to write something like: use OpenOffice::OODoc; my $container = odfContainer("foo.odt"); my $doc = odfDocument ( container => $container, part => 'content' ); then $doc becomes an abstraction of the 'content' part of the document (corresponding to the document body and some automatic styles). This new object brings a lot of methods allowing the applications to retrieve, read, modify, delete and creates elements in the documents. An element is a consistent piece of content or style definition. Any element may contain a text and/or one or more attributes. As an example, the following example selects a paragraph, then gets its text content and the name of its style: my $element = $doc->getElement('//text:p', 2); my $text = $doc->getText($element); my $style = $doc->getAttribute($element, 'style name'); Note that the getElement() method works with XPath expressions. According to the ODF specification, "text:p" specifies a paragraph. The double slash ("//") means "everything from the root of the document". The second argument of getElement() is the position of the needed element in the list (knowing that "//text:p" designates all the paragraphs); this position is zero-based, so in this example the third paragraph is selected. The search space of getElement() is the whole document by default, but it's possible to restrict it to a given context, specified through a additional argument. A context is a particular element, previously selected. As an example, the following code selects the 3rd paragraph in the 4th section (if any): my $section = $doc->getElement('//text:section', 3); my $paragraph = $doc->getElement('//text:p', 2, $section); (Of course, there is a getSection() method that allows you to forget the XPath expression and to retrieve a section by name instead of number.) In a real application, the user doesn't need to known such an XPath expression, because there is a more convenient getParagraph() method that just requires the paragraph number. However, the generic, XPath-based getElement() method remains available in order to retrieve any element that is not covered by a specialized accessor. The getText() method is self-documented in the example. The getAttribute() method requires, after the element itself, the name of the attribute whose value is needed. The real ODF name of the style attribute of a paragraph is "text:style-name"; however, the application may use the "style name" simplified form knowing that getAttribute() is able to translate the attribute names according to a simple logic: every space in the given name is replaced by a "-" and, if no prefix is specified, the prefix of the element itself is used, so "style name" is automatically interpreted as "text:style-name" in this particular context. You don't need to remember the path of such usual objects as paragraphs, headings, lists, images, ..., and other well known document components, because the 3rd layer (see below) provides easy-to-use, predefined accessors for these objects. The text content and the attributes of a selected element may be changed. The following sequence puts a new text content and affects a new style to our previously selected paragraph: $doc->setText($element, "A new text content"); $doc->setAttribute($element, 'style name' => "Text Body Style"); The same layer of the API allows to append of insert new elements. The next example demonstrates the use of appendElement(); it creates a new paragraph with given text and style and appends it to the existing content: $doc->appendElement( 'text:p', text => "Hello world", attributes => { 'style name' => "Text Body Style" } ); For those who hate complex instructions, the 3 lines below do the same job as the example above: my $new_element = $doc->appendElement('text:p'); $doc->setText($new_element, "Hello world"); $doc->setAttribute($new_element, 'style name' => "Text Body Style"); Remember that the changes above are done in the volatile content of document object; up to now; nothing is changed in the corresponding file. In order to commit the changes and make them persistent, we need to call the save() method of the container that has been used to instantiate the document. The API allows the user, in simple situations, to "forget" the ODF container behind the document. The following "hello world" example, that creates and saves a new document, works without explicit use of the odfContainer() constructor: my $doc = odfDocument( file => "foo.odt", create => 'text', part => 'content' ); $doc->appendElement( 'text:p', text => "Hello World !", attributes => { 'style name' => "Text Body Style" } ); $doc->save; Note that odfDocument() is used here with a 'file' parameter, whose value is a file name, instead of a 'container'. At the end, save() is called from the document instance itself instead of a container. However, a container is always instantiated, but it's just hidden; and save() is only a stub method, the real job being done by the save() method of the container. Such a shortcut is useful in this example because the program processes one part only, i.e. the content; for applications that uses more than one part (content, styles, meta- data), two or more document connectors must be instantiated in association with the same container connector, and, as a consequence, the explicit use of odfContainer() is recommended. OpenOffice::OODoc::XPath allows some quick element manipulation and exchange, and can operate on several documents in the same session. For example: my $doc1 = odfDocument(file => 'file1.odt', part => 'content'); my $doc2 = odfDocument(file => 'file2.odt', part => 'content'); my $paragraph = $doc1->getElement('//text:p', 15); $doc2->insertElement ('//text:h', 0, $paragraph, position => 'after'); This sequence takes an arbitrary paragraph (the 16th one) of a document and inserts it immediately after an arbitrary heading (the first one) in another document. Here, we used an insertElement() method to directly transfer an existing text element, but the same method (with different arguments) can create a new element according to application data, or from a well- formed XML string describing any document element in regular Open Document syntax. Example: # a program my $doc = odfDocument(file => 'file1.odt', part => 'content'); open MYFILE, "> transfer.xml"; print MYFILE $doc1->exportXMLElement('//text:p', 15); close MYFILE; # another program my $doc2 = odfDocument(file => 'file2.odt', part => 'content'); open MYFILE, "< transfer.xml"; $doc2->insertElement ('//text:h', 0, , position => 'after'); close MYFILE; These last two short programs produce the same effect as the preceding one, but the target file can be processed later than the source one and in a different location, because there is no direct link in the two documents. The first program exports an XML description of the selected element, then the second program uses this description to create and insert a new element that is an exact replicate of the exported one. In the meantime, the XML intermediate file can be checked, processed and transmitted with any language and protocol. The OpenOffice::OODoc::XPath manual page describes a lot more common features that may be used through the document-oriented API introduced below. But it's just a beginning, because, in the real world, you have to do much more sophisticated processing, and you have not a lot of time to learn the XML path of any kind of document element (paragraph, heading, item list, table, draw frame, style, ...). =head2 Document-oriented API So there is a third, more user-friendly layer, that should be the only one visible for most of the applications. The third layer is designed as a set of application-oriented classes, inherited from OpenOffice::OODoc::XPath. In this layer, the basic principle is "allow the user to forget XML". Each document element is considered from the user's point of view, and the XML path to get it is hidden. This approach works only if a specialized OpenOffice::OODoc::XPath class is defined for each kind of content. So, we ultimately need the following classes: OpenOffice::OODoc::Text for the textual content of any document; OpenOffice::OODoc::Image to deal with the graphic objects; OpenOffice::OODoc::Styles for page/style definitions; OpenOffice::OODoc::Meta for the metadata (meta.xml); Fortunately, the 3 first ones should not be expressly used in real applications, knowing that the toolbox provides a compound OpenOffice::OODoc::Document class which inherits all their features. As a consequence, ordinary users have just to deal with OpenOffice::OODoc::Document to process any content (graphic or textual) or layout. An OpenOffice::OODoc::Document object is instantiated through the odfDocument() function, that is a shortcut for OpenOffice::OODoc::Document->new(). For other parts, such as the metadata or the file manifest, other constructors are available. Simply put, a typical application will need OpenOffice::OODoc::Document in order to process the content and the layout, and OpenOffice::OODoc::Meta for a read/write access to the global properties. However, the reference manual in organized according to the kind of features, in order to avoid a huge manual page for the Document class. As a consequence, the documentation of this compound class includes 4 chapters (::Text, ::Styles, ::Image and ::Document, the last one describing a few transverse methods. In addition, the user should remember that all the low-level attributes and methods described in the ::Xpath manual chapter are inherited by both ::Document and ::Meta. The OpenOffice::OODoc::Text class brings some table processing methods (table creation, direct access to individual cells). These methods, (under some conditions) can be used with spreadsheets (ODF spreadsheet documents) as well as with tables included in text documents. To illustrate the differences between the layers, the two following instructions are equivalent: print $doc->getText('//text:p', 2); print $doc->getParagraphText(2); provided that $doc has been previously created through an odfDocument() call. The difference looks tiny, but in fact OODoc::Text contains much more sophisticated text-aware methods that avoid a lot of coding and probably a lot of errors. For example, the following code puts the content of an ordinary perl list (@mydata) in an ODF document as an regular item list: my $list = $doc->appendItemList(); $doc->setText($list, @mydata); The first instruction creates an empty list at the end of the document body. The second one populates the new list with the content of an application- provided table. The setText() method automatically modify its behaviour according to the functional type of its first argument (with is not the same for a paragraph as for an itemlist or a table cell). The same layer provides some global processing methods such as: my $result = $doc->selectTextContent($filter, \&myFunction); that produces a double effect: 1) it scans the whole document body and extracts the content of every text element matching a given filter expression (that is an exact string or a conventional Perl regular expression); 2) it triggers automatically an application-provided function each time a matching content is found; the called function can execute any on-the-fly search/replace/delete operation on the current content and get data from any external database or communication channel; the return value of the function automatically replaces the matching string. So such a method can be used in sophisticated conditional fusion- transformation scripts. But you can use the same method to get a flat ASCII export of the whole document, without other processing, if you provide neither filter nor action: print $doc->getTextContent; Of course, OODoc can process presentation and not only content. Example: $filter = 'Dear valued customer'; foreach $element ($doc->selectElementsByContent($filter)) { $doc->setStyle($element, 'Welcome') if $element->isParagraph; } After this last code sequence, every paragraph containing the string 'Dear valued customer' has the 'Welcome' style (assuming 'Welcome' is a paragraph style, already defined or to be defined in the document). A style (like any other document element) can be completely created by program, or imported (directly or through an XML string) from another document. The second way is generally the better because you need a lot of parameters to build a completely new style by program, but the creation of a simple style is not a headache with the OODoc::Styles module, provided that you have an ODF attributes glossary at hand. The following example show the way to build the "Welcome" style. This piece of code declares "Welcome" as a paragraph style, whith "Standard" as parent style, and with some private properties (Times 16 bold font and navy blue foreground). $doc->createStyle ( "Welcome", family => 'paragraph', parent => 'Standard', properties => { 'area' => 'text', 'style:font-name' => 'Times', 'fo:font-size' => '16pt', 'fo:font-weight' => 'bold', 'fo:color' => '#000080' } ); The color attributes are encoded in RGB hexadecimal format. It's possible to use more mnemonic values or symbols, through conversion functions provided by the Styles module, and optional user-provided colour maps. For example, "#000080" could be replaced by odfColor('navy blue'), provided that an appropriate color table is available at the run time; see odfLoadColorMap() in the OpenOffice::OODoc::Styles manual chapter. According to the application logic, each newly created style can be registered either as a "named" style (i.e. visible and reusable through a typical office software suite) or as an "automatic" style. For an ordinary application that needs the best processing facility for any kind of content and presentation element, the OODoc::Document module is the best choice. This module defines a special class that inherits from Text, Image and Styles classes. It allows the programmer, for example, to simply insert a new paragraph, create an image object, anchor the image to the paragraph, then create the styles needed to control the presentation of both the paragraph and the image, all that in the same sequence and in any order. Caution: In order to get a convenient translation between the user's local character set and the common ODF encoding (utf8), the application must indicate the appropriate encoding. The default one is iso-8859-1 in the CPAN distribution; it can be set using the odfLocalEncoding() function. Example: use OpenOffice::OODoc; odfLocalEncoding 'iso-8859-15'; The default encoding can be selected by the user during the installation, and changed later by editing a configuration file. In addition, a program working with several documents in the same time can select a distinct character set for each one. =head1 Some practical uses To begin playing with the modules, you should before all see the self-documented sample scripts provided in the package. These scripts do nothing really useful, but they show the way to use the modules. You should directly load the full library with the single "use OpenOffice::OODoc" in the beginning of your scripts. Then you should only use (in the beginning) the Document and/or Meta classes only. We encourage you, in the first time, to avoid any explicit OODoc::XPath basic method invocation, and to deal only with available "intelligent" modules (Text, Image, Styles, via Document, and Meta), in order to get immediate results with a minimal effort. And, if you use this stuff for evangelization purpose, you can show the code to prove that the OpenDocument format allows a lot of things with a few lines. You can avoid the heavy object oriented notation such as: my $meta = OpenOffice::OODoc::Meta->new(file => "xxx.ods"); and use the shortcuts like: my $meta = odfMeta(file => "xxx.ods"); The first thing you have to do with a document is to create an object focused on the member you want to work with, and "feed" it with regular ODF XML. The most straightforward way to do that is to create the object in association with an ODF file. =head2 Dealing with metadata We need metadata access, so we use OODoc::Meta use OpenOffice::OODoc; my $doc = odfMeta(file => 'myfile.odt'); my $title = $doc->title; if ($title) { print "The title is $title"; } else { print "There is no title"; } Here, because the constructor of OODoc::Meta is called with a 'file' parameter, OODoc::Meta knows it needs a file access and it dynamically requires the OODoc::File module, instantiates a corresponding object using the file name, connects to it, and asks it for the 'meta.xml' member of the file. All that annoying processing is hidden for the programmer. We have just to query for the useful object, the title. In the same way, we could get (or even change) the document creation or last modification date registered by the editing software: my $d1 = $doc->creation_date; my $d2 = $doc->date; The dates, in the ODF documents properties, are stored in ISO-8601 format (yyyy-mm-ddThh:mm:ss); this format is readable but not necessarily convenient for any application. But the API provides easy to use tools allowing conversion to or from the regular numeric time() format of the system, allowing any kind of formatting or calculation. We could get more complex metadata structures, such as the user defined fields: my %ud = $doc->user_defined; foreach my $name (keys %ud) { print $name . '->' . $ud{$name} . "\n"; } This code captures the user defined fields (names and values) in a hash table, which then is displayed in a "name->value" form. You could see the way to update the user defined fields in the 'set_fields' script, provided with the distribution. The most usual metadata accessors have a symmetrical behaviour. To update the title, for example, you have to call the 'title' method with a string argument: $doc->title("New title"); You can proceed in the same way with subject, description, keywords. The 'keywords' is an example of polymorphic behaviour (which is quite common for many OODoc methods): my $keywords = $doc->keywords; my @keywords = $doc->keywords; In the first form, the keywords are returned concatenated and comma- separated in a single editable text line. In the second one, we get the keywords as a list. But if 'keywords' is called to add new keywords, these ones must be provided as a list: $doc->keywords("kw1", "kw2", "kw3"); $doc->keywords(@my_keywords); The program is automatically prevented from introducing redundancy in the keyword list (the 'keywords' method deletes duplicates). While 'keywords' can only add new keywords, you have to call removeKeyword to delete an existing keyword. If you want to destroy the entire list of keywords in a single call, you have just to write: $doc->removeKeywords; Well, we have done some updates in the metadata, but these updates apply only in memory. To make it persistent in the file, we have just to issue a: $doc->save; I said OODoc::Meta (which is an OODoc::XPath) did not know anything about the OpenDocument compressed files. But in my example,the object has been created with a 'file' argument and associated with an implicit OODoc::File object. So, the 'save' method of OODoc::XPath is only a stub method which sends a 'save' command to the connected OODoc::File object. With an object created with an 'xml' parameter (providing the metadata through an XML string, without reference to a file), a 'save' call generates a 'No archive object' error. However, if the object had been created from an XML flat file (instead of a regular ODF compressed file), the output would be a flat XML file as well. Note: A document is always saved in the same file format as it's source. The save() can't act as a format converter. So, you can't save an OOo 1.0 file in OASIS OpenDocument format and vice versa, and you can't directly (without intermediate processing) save in ODF compressed format a document loaded from XML data. However, thanks to the getXMLContent() method, you can write the flat XML to the standard output or a given file handle. If you prefer to keep the original file unchanged, you can issue a $doc->save('my_other_file.odt'); that produces the same thing as 'File/SaveAs' in your favorite office software: if called with an argument, 'save' creates a new file containing all the changed and unchanged members of the original one. Of course, whatever the way you will use (or not use) the save() method, you will never process valuable documents without a backup copy... =head2 Example 2 - Manipulating text Here we must read and update some text content elements. By "text content", we mean not only "flat text". While the most interesting module is named OpenOffice::OODoc::Text, it's not fully dedicated to text documents. It can deal with the text content of presentations, as well as the sheets and cells of a spreadsheet. Our program begins with something like that: use OpenOffice::OODoc; my $doc = odfDocument(file => 'myfile.odt'); The second line produces an OpenOffice::OODoc::Document object, which inherits from O::O::Text, O::O::Image and O::O::Styles. However, in the present example, we'll use its O::O::Text features only. To give a very high level abstract, we can say that OODoc::Text provides 2 kinds of read access methods: - the 'get' methods that return data referred by unconditional addressing, like getParagraph(4); - the 'select' methods that return data selected against a given filter, related to a text content or an attribute value, like selectParagraphsByStyle('Text body'). Some 'get' or 'select' methods return lists while other return individual elements or values. Returned data may be elements or texts. Text data can be exported or displayed, but the application needs elements to do any read/write operation on the content. For example: my $text = $doc->getTextContent; extracts the whole content of the document as a flat, editable text in the local character set, for immediate use (or display on a dumb terminal). Of course, there are more the one way to do the same thing, so you can get the same result with a 'select' method as with a 'get' one if you use a "non-filtering filter". So: my $text = $doc->selectTextContent('.*'); will also return the whole text content. But this last method, with some additional arguments and an appropriate filter, is much more powerful, because it can do 'on-the-fly' processing in each text element matching the filter (for example, insert values extracted from an enterprise database or resulting from complex calculations). The output of getTextContent can be tagged according to the type of each text element, so the application can easily use this method to export the text in an alternative (simple) markup language. To do some intelligent processing in the text, we need to deal with individual text objects such as paragraphs, headings, list items or table cells. For example, to export the content of the 5th paragraph (paragraph numbering beginning with 0), we could directly get the text with: my $text = $doc->getParagraphText(4); But in order to update the same paragraph, or change its style, I need the paragraph element, not only its text content: my $para = $doc->getParagraph(4); # text processing takes place here $doc->setText($para, $other_text); $doc->setStyle($para, $my_style); Some methods can dynamically adapt to the text element type they have to process. For example, the getText method (exporting the text content of a given text element), can return the content of many kinds of element (paragraphs, headings, table cells, item lists or individuals list items). In addition, any text content extracted with an high-level OODoc method is transcoded in the local character set (UTF-8 issues are (we hope) hidden for the application). Optionally, the text output can be instrumented with begin and end application-provided tags according to the element type (so it's possible to export the text in an alternative, simple XML dialect, or in LaTeX, or in an application-specific markup language). In order to facilitate some kinds of massive document processing operations, OODoc::Text provides a few high level methods that do iterative processing upon whole sets of text elements. One example is selectElementsByContent: this method looks for any text container matching a given pattern (string or regular expression) and, each time an element is selected, it executes an application-provided callback function. An example of use is provided in the 'search' demo script, which selects any text element in a document matching a given expression, and appends the selected content as a sequence of paragraphs in another document. The more usual methods have explicit names, and can be used without their exhaustive documentation, provided that the programmer has a good understanding of the general philosophy. Heading and paragraph manipulations are quite simple. The situation is more complex with other text content such as item lists, tables and graphics. To get an individual list item, you must point to it from a previously obtained list element: my $item_list = $doc->getList(2); my $item = $doc->getListItem($item_list, 4); Here, $item contains the 5th item of the 3rd list of the document. The content of the item could then be exported by a generic method such as getText(), or processed using another method. Note that, if the application doesn't need the $item_list object for any other use, it can directly get the list item with the same method with a list number (instead of s list object) as its first argument: my $item = $doc->getListItem(2, 4); =head2 Playing with tables and spreadsheets Because the need of data capture within table structures is more evident, there is a direct accessor to get any individual table cell: my $value = $doc->getCellValue($table, $line, $col); For example: my $value = $doc->getCellValue(0, 12, 0); This code example returns the value of the 1st cell of the 13th row of the 1st table in the document. Note the 'cell value' is simply the text content if the cell type is string; but if the cell type is any numeric type, getCellValuereturns the content of the value attribute and ignores the text. The first argument (the table) can be either the table number (zero-based, according to its sequential position in the document) or the logical table name (as it's get or set by the end-user with OOo Writer or Calc). A cell can be selected in a table using either it's numeric (row, column) coordinates or a "spreadsheet-like" alphanumeric notation. So, the example above could be written as my $value = $doc->getCellValue(0, "A11"); Caution, in the classical spreadsheet notation, the column comes first while it comes last in the numeric coordinates. In addition, knowing that the numeric coordinates are zero-based, "A1" corresponds to (0,0). Finally, remember that the alphanumeric coordinates must be provided in a single string while numeric coordinates require two arguments. This alphanumeric notation is probably more user-friendly for OOo Calc documents, but it's allowed by OODoc whatever the document class: you can use it with tables in text documents as well. Caution: The direct cell addressing works only when the table XML storage is "normalized", i.e. when every table object (row, column or cell) is mapped to an exclusive XML element. The application program can easily ensure this "normalization" thanks to the normalizeSheet() method, described in the OpenOffice::OODoc::Text manual page. However, up to now, the tables included in text document through OpenOffice.org Writer are normalized, so they are immediately available for direct addressing. In the other hand, with OpenOffice.org Calc spreadsheets, several contiguous objects are mapped to a single XML element as long as they have the same content, the same type and the same presentation. It's not an issue; it's a feature allowed by the OpenDocument specification in order to save storage space, knowing that typical large spreadsheets contain a lot of empty, or repetitive, cells. As a consequence, several cells may be located at the same coordinates. The normalizeSheet() method allows the application to define a safe area, sized according to its needs, where the direct object addressing works whatever the XML storage method in use. The table-related methods can be used with spreadsheets (i.e. OOo Calc documents) as well as with tables included in text documents. However, before addressing cells in a spreadsheet document, a program must "declare" the size of the used area in each target sheet (this requirement is due to performance considerations, for Calc documents only). You can also change the content of a cell: $doc->updateCell($table, $line, $col, $value); $doc->updateCell($table, $line, $col, $value, $string); $doc->updateCell($cell, $value); $doc->updateCell($cell, $value, $string); The first form puts the $value in the target cell, assuming it's a string cell or, if it's a numeric one, your choice is to put the same content as the value and the displayable string. The second form (assuming the target cell is numeric) provides independent content for value and string (the programmer must know what (s)he does, for example in case of currency or date cell). The 3rd and 4th forms do respectively the same things, but use a previously obtained cell element in place of 3D coordinates (in order to avoid unnecessary low-level XPath recalculation). For a flat text (non-numeric) cell whose the reference is already available, setText() produces the same result as updateCell(): my $cell = $doc->getCell($table, $row, $col); $doc->setText($cell, "The text in the cell"); Both getCellValue() and updateCell() can be replaced by the cellValue() shortcut, that is a read/write accessor to indivudual cells. So: my $value = $doc->cellValue("Sheet4", "B12"); $doc->cellValue("Sheet1", "P5", $value); copies a value from one cell to another one in another table. In this intro, the cells are assumed to be text-only. Of course, the code is more complex with numeric cells, because the program have to get or set some additional information, according to its data type. OODoc::Text allows the program to create a new table, using the appendTable or insertTable method. The following example appends a new table with 8 lines and 5 columns to the document. my $table = $doc->appendTable("MyTable", 8, 5); But this new table is (by default) a pure text table. It's possible to build very sophisticated table structures, with an appropriate data type and a special presentation for each cell. But, to complete this task, the application must provide a lot of parameters. So, it's recommended to avoid purely programmatic table construction, and to reuse existing table structures and styles in template documents previously created with an ODF compatible software. =head2 Sections, subdocuments and hyperlinks For sophisticated document structures, paragraphs and other text containers may be included in sections. The API allows the applications to easily create or retrieve sections, whith the getSection(), appendSection(), and insertSection() methods. A given section may be either populated with a local content or provided with an external link (file path or URL) in order to include a subdocument. In addition, using lockSection() and unlockSection(), the programs can control the end-user write protection of any section. The following example (working with OOo 2.0) appends to a master document a new, write-protected section including a new document which can be reached through an internet link: my $url = "http://jean.marie.gouarne.online.fr/doc/oodoc_guide.odt"; $doc->appendSection ( "Getting Started", link => $url, protected => "true" ); And, if an unfortunate end-user is barred from updating a section by a lost password, the programmer can help with a single line such as: $doc->unlockSection($section_name); Of course, a section can host any local content instead of an external link. my $section = $doc->appendSection("Section 1"); $doc->appendParagraph ( attachment => $section, text => "The first paragraph in the section", style => "Standard" ); Here, a section is created and receives a paragraph as its first content. An existing set of content elements could migrate under a section. The next example, more sophisticated, selects the list of all the elements that hierarchically depend on the first level 1 title of the document and moves these elements to a given section: my @content = $doc->getChapterContent(0, level => 1); $doc->moveElementsToSection("Section 1"); The sections are not the only places for using hyperlinks. The applications can associate hyperlinks to any portion of text. The following example puts a remote (http) link on every "OpenDocument" character string in a given paragraph: $doc->setHyperlink ($para, "OpenDocument", "http://www.oasis-open.org"); The target of an hyperlink may be a bookmark or a heading in the current document or in another ODF document. For example, if the target is a bookmark included in the same document, the link is the name of the bookmark with a leading "#": $doc->setHyperlink($para, "a string", "#MyMark"); When the target is a heading (i.e. a hierarchical title), the link is made of the text of the heading, prefixed with "#" and suffixed by "|outline". If an hyperlink is aimed at any target belonging to another document (in the local filesystem or elsewhere), you have just to concatenate the file path and the internal path. The example below puts an hyperlink to a particular heading located in a remote document: $doc->setHyperlink ( $para, "read the conclusion", "http://somewhere.com/somewhat.odt#Conclusion|outline" ); =head2 Manipulating variables, bibliographic entries, bookmarks The OODoc toolbox provides easy read/write accessors to some useful objects that can be included in OOo text documents. If a text document contains a user-defined field, the corresponding value can be read and updated. For example, if the user needs to increase a numeric by a given value, the corresponding code could be: $old_value = $doc->userFieldValue("FieldName"); $doc->userFieldValue("FieldName", $old_value + $added_value); In addition, the OODoc API allows the user to "declare" new user-defined fields if needed (see setUserFieldDeclaration() in OpenOffice::OODoc::XPath). Any OpenDocument-compliant variable text field may be inserted in a document through the textField() method. The next example appends a paragraph whose text content is "This document contains pages", knowing that the real page count will be dynamically displayed by the office software: my $p = $doc->appendParagraph (text => "This document contains "); $doc->appendElement($p, $doc->textField('page-count')); $doc->extendText($p, " pages"); While the sequence above appends a text field at the end of a paragraph, the setTextField() method may insert a text field anywhere within an existing paragraph according to various positioning parameters. The example hereafter creates a date field immediately after the last occurrence of the substring "the current date is "; the 'after' option provides the search string while the 'way' option specifies that it must be searched backward: $doc->setTextField( $paragraph, 'date', after => "the current date is ", way => 'backward' ); It's possible to get or set any property of a bibliography entry. An entry can be selected by its identifier (as it appears for the end-user). The first example below prints the title and the author of the first found occurrence of a "[GEN99]" entry, while the second one creates (or updates) its "ISBN" and "pages" properties: # 1 my %properties = $doc->bibliographyEntryContent("GEN99"); print "Title = $properties{'title'}\n"; print "Author = $properties{'author'}\n"; # 2 $doc->bibliographyEntryContent ( "GEN99", isbn => 'xxxxyyyyzzzz', pages => 254 ); In addition, a getBibliographyEntries() method allows the user to retrieve the full list of the entries included in a document. An additional bibliography entry may be inserted within a paragraph using setBibliographyMark(). As an example, the following instruction inserts a new bibliography mark as a replacement of the first substring "reference needed" that may occur after the 20th character in a given paragraph: $doc->setBibliographyMark ( $paragraph, offset => 20, replace => "reference needed", attributes => { identifier => "JDE", title => "OASIS OpenDocument Essentials", author => "J. David Eisenberg", year => 2005, isbn => "1-4116-6832-4" } ); We can put a bookmark in a paragraph containing a given string. Example: my $paragraph = $doc->selectElementByContent("my search string"); $doc->setBookmark($paragraph, "MyMark"); The instruction above puts the mark at the beginning of the paragraph; however, setBookmark() could put the mark at any position within the text, according to optional parameters. To illustrate the positioning logic, the following instruction puts the bookmark immediately after the first occurrence of "xyz" that appear after the first 20 characters: $doc->setBookmark( $paragraph, "MyMark", offset => 20, after => "xyz" ); Note that there are many possible positioning parameter combinations for bookmarks and any other markup elements intended to be inserted within text containers; the various possibilities are inherited from the setChildElement() method, that is described in the OpenOffice::OODoc::XPath manual page. A bookmark (created either through OpenOffice::OODoc or through this Perl API) can be used to retrieve a text element: my $paragraph = $doc->selectElementByBookmark("MyMark"); Note that the insert position of text fields, bibliography marks, bookmarks, and other markup elements may be specified using the same set of position parameters and according to the same logic, that are inherited from the common setChildElement() method, described in OpenOffice::OODoc::XPath. =head2 Dealing with text AND metadata Sometimes we must access both the text content and the metadata. So, we need two OODoc::XPath objects : one OODoc::Document and one OODoc::Meta. And to avoid collisions and inefficient I/O operations, we need to connect the 2 objects with the same OODoc::File "server". use OpenOffice::OODoc; my $archive = odfContainer('myfile.odt'); my $content = odfDocument(container => $archive); my $meta = odfMeta(container => $archive); # process content and metadata $archive->save; In this case, the $content and $meta are explicitly linked to a common container. As a consequence, when the save() method of this container is triggered, all the changes through them are made persistent. There is an example of simultaneous access to content and metadata in the script 'set_title' (where some text content is used to generate a piece of metadata). =head2 Manipulating graphics The module OODoc::Image brings some functionalities that can be used against any OO document. The following code (combining the capabilities of OODoc::Text and OODoc::Image) selects the first paragraph containing the string "OpenOffice" and attach an imported image to it. my $p = $doc->selectElementByContent("OpenOffice"); die "Paragraph not found" unless $p; $doc->createImageElement ( "Paris landscape", description => "Montmartre in winter", attachment => $p, import => "C:\MyDocuments\montmartre.jpg", size => "5cm, 3.5cm", style => "graphics2" ); In a spreadsheet document, the same image could be attached to a cell instead of a paragraph; to do so, the "attachment" option should be set to a cell element, previously obtained using getCell(). With the same syntax, in a presentation document, the "attachment" should be a draw page, previously selected using getDrawPage(). A "page" option allows the user to anchor an image to a page, instead of attaching it to a text container. In this example, the image is physically imported. But I could replace the "import" parameter by a "link" one, in order to use the image as an external link (cf. the "link" option when you insert an image in OpenOffice.org). This link could use a local filesystem path as well as a remote access path such as "http://...". My new image needs a style (called "graphics2" in my example) to be presented. This style could be an existing one, but my program could create it if needed, using an OODoc::Styles method (see below). Any characteristic of an existing image can be read or updated using simple methods. For example, it's easy to change the size and the position of my image: $doc->imageSize("Paris landscape", "10cm, 7cm"); $doc->imagePosition("Paris landscape", "3cm, 0cm"); The size and position strings indicate the used length unit. OODoc doesn't the provided unit, so the application should ensure that only ODF-compliant units are used. Possible units are, for example, "cm" (centimeter), "mm" (meter), "in" (inch), "pt" (point). The logical name of the image (here "Paris landscape") is the best way to retrieve an image object, so it's a mandatory argument with the createImageElement method. With OpenOffice.org Writer, each image is created with an unique name (that is "Image1", "Image2", etc. if the user doesn't provide a more significant one). But with OpenOffice.org Impress, the images are unnamed by default. We recommend you to give a significant name to each object that you want to process later by program, knowing that if an object can be easily caught by program, it's potentially reusable. An image can be selected by his description (i.e. the text the end-user can edit in the image properties dialog in OpenOffice.org). So, the following sequence provides the list of images whose the description contains the string "Montmartre": my @images = $doc->selectImageElementsByDescription("Montmartre"); If you have to store and process a graphical content out of the end user's editing software, you can export it as an ordinary file: $doc->exportImage("Paris landscape", "/home/pictures/montmartre.jpg"); And you can use a symmetric importImage method to change the content of an image element. =head2 Managing styles The OODoc::Styles allows the programmer to get any style definition, to change it and, if really needed, to create new styles. In the first part of this document, you can see an example of paragraph style creation. Unfortunately, createStyle could drive you to heavy coding efforts, because a very sophisticated style definition needs a lot of parameters and requires the knowledge of a lot of ODF attribute names. So we recommend you to systematically reuse existing styles (stored in ODF template documents used as "style repositories" or in XML databases). The createStyle method supports a "prototype" parameter that allows you to clone an existing style, contained in the same document or in another one. The next code sequence selects the "Text body" style of a document, and uses it as a template to create a "My Text body" style in another document, changing the font size only: my $template = $doc1->getStyleElement("Text body"); $doc2->createStyle ( "My Text Body", family => "paragraph", prototype => $template, properties => { "area" => "text", "fo:font-size" => "12pt", "fo:color" => odfColor("dark blue") } ); Here a "dark blue" color has been given to the text; but "dark blue" is an arbitrary string, that must be present in a user-provided, previously loaded color map; without this color map, the users must, at their choice, either directly provide an hexadecimal, six-digit color code, with a leading "#" (such as "#00008b", that is the translation of "dark blue" in my installation), or get it through the odfColor() function with 3 decimal RGB values as arguments. Because a style is required for each image in a document, the OODoc::Document brings a more user-friendly createImageStyle method. This method allows you to create an image style without any mandatory parameter (excepted the name). So, the "graphics2" style I invoked in a previous createImage example could be simply created by: $doc->createImageStyle("graphics2"); Without other indication, the module automatically creates a style with "reasonable" values, so the image is really visible in the document. Of course, the application could provide explicit values for some parameters if needed. The following call, for example, provides specific values for contrast, luminance and gamma correction: $doc->createImageStyle ( "graphics2", properties => { 'draw:contrast' => '2%', 'draw:luminance' => '-3%', 'draw:gamma' => '1.1' } ); Styles are not made only to control the presentation of individual elements. There are special styles for page layout. While these styles are described with very specific data structures, the OODoc::Styles module contains some methods dedicated to page styling. =head2 Dealing with styles AND content While the OpenOffice::OODoc::Document methods can process both the content (text, complex structures and graphics) and the styles, it's not always possible any style and any content through the same object in the same session. Each individual instance of ::Document wraps an indivudual part of an ODF package. The default part is "content.xml", but all the named style definitions are stored in the "styles.xml" part (in a few words, a named style is a style which was designed in order to be used by more than one content element; for example, any style which could be selected through the style dialog box of a typical user-oriented office software is a "named" style). In order to avoid a lot of useless XML parsing, only one part at a time is loaded. As a consequence, if the application needs to process content and named styles during the same session, it must create 2 instances of ::Document objects, associated with the same ODF container. Each instance must be associated with the appropriate target. For example: use OpenOffice::OODoc; my $archive = odfContainer('myfile.odt'); my $content = odfDocument ( container => $archive, part => 'content' ); my $styles = odfDocument ( container => $archive, part => 'styles' ); After this sequence, the $styles object gives access to any named style while all the document body can be processed through the $content object. Note that in this last example, we could avoid the "part" option for the "content" member of the package (because "content" is the default). Knowing that its always possible to process content, named styles and metadata in the same session, we could instantiate a ::Meta object through odfMeta() as well. So up to 3 connecting objects can be used as interfaces for the same ODF file. Of course, a single $archive->save() can make persistent all the changes made through all the connected objects. =head1 COMMENTS AND BUG REPORTS Comments, questions and answers are welcome through the CPAN forum L Bug reports should be sent using L =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2010 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/config.xml0000644000175000017500000000070711416644373015564 0ustar jmgjmg OpenOffice::OODoc local configuration file 2 utf8 . 2010-07-12T18:55:23 N/A OpenOffice-OODoc-2.125/OODoc/Image.pm0000644000175000017500000003726511415443245015160 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : Image.pm 2.020 2009-07-07 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2009 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- package OpenOffice::OODoc::Image; use 5.008_000; use strict; use OpenOffice::OODoc::XPath 2.237; use File::Basename; our @ISA = qw ( OpenOffice::OODoc::XPath ); our $VERSION = '2.020'; #----------------------------------------------------------------------------- # default attributes for image style our %DEFAULT_IMAGE_STYLE = ( references => { 'style:family' => 'graphics', 'style:parent-style-name' => 'Graphics' }, properties => { 'fo:clip' => 'rect(0cm 0cm 0cm 0cm)', 'style:vertical-rel' => 'paragraph', 'style:horizontal-rel' => 'paragraph', 'style:vertical-pos' => 'from-top', 'style:horizontal-pos' => 'from-left', 'draw:color-mode' => 'standard', 'draw:luminance' => '0%', 'draw:red' => '0%', 'draw:green' => '0%', 'draw:blue' => '0%', 'draw:gamma' => '1', 'draw:color-inversion' => 'false' } ); #----------------------------------------------------------------------------- # constructor : calling OO XPath constructor sub new { my $caller = shift; my $class = ref ($caller) || $caller; my %options = ( @_ ); my $object = $class->SUPER::new(%options); return $object ? bless $object, $class : undef; } #----------------------------------------------------------------------------- # create & insert a new image element sub createImageElement { my $self = shift; my $name = shift; my %opt = @_; my $content_class = $self->contentClass; my $container = $self->{'image_container'}; my $attachment = undef; my $firstnode = undef; my $element = undef; my $description = undef; my $size = undef; my $position = undef; my $import = undef; my $path = undef; if ( ($content_class eq 'presentation') or ($content_class eq 'drawing') ) { my $target = $opt{'page'} || ''; my $page = ref $target ? $target : $self->getNodeByXPath ("//draw:page[\@draw:name=\"$target\"]"); delete $opt{'page'}; $path = $page; } else { $path = $opt{'attachment'}; } delete $opt{'attachment'}; unless ($path) { $attachment = ($self->{'body'}) || ($self->getElement('//style:header', 0)) || ($self->getElement('//style:footer', 0)); if ($attachment && defined $opt{'page'}) { $firstnode = $self->selectChildElementByName ($attachment, 'text:(p|h)'); } } else { $attachment = ref $path ? $path : $self->getElement($path, 0); } unless ($attachment) { warn "[" . __PACKAGE__ . "::createImageElement] No valid attachment\n"; return undef; } # parameters translation $opt{'draw:name'} = $name; if ($opt{'description'}) { $description = $opt{'description'}; delete $opt{'description'}; } if ($opt{'style'}) { $opt{'draw:style-name'} = $opt{'style'}; delete $opt{'style'}; } if ($opt{'size'}) { $size = $opt{'size'}; delete $opt{'size'}; } if ($opt{'position'}) { $position = $opt{'position'}; $opt{'text:anchor-type'} = 'paragraph'; delete $opt{'position'}; } if ($opt{'link'}) { $opt{'xlink:href'} = $opt{'link'}; delete $opt{'link'}; } if ($opt{'import'}) { $import = $opt{'import'}; delete $opt{'import'}; } if ($opt{'page'}) # create appropriate parameters if anchor=page { # and insert before the 1st text element $opt{'text:anchor-type'} = 'page'; $opt{'text:anchor-page-number'} = $opt{'page'}; $opt{'draw:z-index'} = "1"; $element = $firstnode ? # is there a text element ? $self->insertElement # yes, insert before it ( $firstnode, $container, position => 'before' ) : # no, append to parent element $self->appendElement ( $attachment, $container ); delete $opt{'page'}; } else { if ($path) # append to the given attachment if any { $element = $self->appendElement ($attachment, $container); } else # else append to a new paragraph at the end { my $p = $self->appendElement($attachment, 'text:p'); $element = $self->appendElement($p, $container); } } if ($self->{'opendocument'}) { my $img = $self->appendElement($element, 'draw:image'); foreach my $a (keys %opt) { if ($a =~ /^xlink:/) { $self->setAttribute($img, $a, $opt{$a}); delete $opt{$a}; } } } $self->setAttributes($element, %opt); $self->setImageDescription($element, $description) if (defined $description); $self->setImageSize($element, $size) if (defined $size); $self->setImagePosition($element, $position) if (defined $position); $self->importImage($element, $import) if (defined $import); return $element; } sub insertImageElement { my $self = shift; return $self->createImageElement(@_); } #----------------------------------------------------------------------------- # image list sub getImageElementList { my $self = shift; return $self->getElementList($self->{'image_xpath'}, @_); } #----------------------------------------------------------------------------- # select an individual image element by name sub selectImageElementByName { my $self = shift; my $text = shift; unless ($self->{'opendocument'}) { return $self->selectNodeByXPath ("//draw:image\[\@draw:name=\"$text\"\]", @_); } else { my $frame = $self->selectFrameElementByName($text); my $child = $frame->first_child('draw:image'); return $child ? $frame : undef; } } #----------------------------------------------------------------------------- # select a list of image elements by name sub selectImageElementsByName { my $self = shift; return $self->selectElementsByAttribute ($self->{'image_xpath'}, 'draw:name', @_); } #----------------------------------------------------------------------------- # select a list of image elements by description sub selectImageElementsByDescription { my $self = shift; my $filter = shift; my @result = (); foreach my $i ($self->getImageElementList) { my $desc = $self->getXPathValue($i, 'svg:desc'); push @result, $i if ($desc =~ /$filter/); } return @result; } #----------------------------------------------------------------------------- # select the 1st image element matching a given description sub selectImageElementByDescription { my $self = shift; my $filter = shift; my @result = (); foreach my $i ($self->getImageElementList) { my $desc = $self->getXPathValue($i, 'svg:desc'); return $i if ($desc =~ /$filter/); } return undef; } #----------------------------------------------------------------------------- # gets image element (name or ref, with type checking) sub getImageElement { my $self = shift; my $image = shift; return undef unless $image; my $element = undef; if (ref $image) { $element = $image; } else { $element = ($image =~ /^\//) ? $self->getElement($image, @_) : $self->selectImageElementByName($image, @_); } return undef unless $element; my $name = $element->name; return ($name eq $self->{'image_container'}) ? $element : undef; } #----------------------------------------------------------------------------- # basic image attribute accessor sub imageAttribute { my $self = shift; my $image = shift; my $attribute = shift; my $value = shift; my $element = $self->getImageElement($image); if ($self->{'opendocument'} && ($attribute =~ /^xlink:/)) { $element = $element->first_child('draw:image'); } return undef unless $element; return (defined $value) ? $self->setAttribute($element, $attribute => $value) : $self->getAttribute($element, $attribute); } #----------------------------------------------------------------------------- # selects image element by image URL sub selectImageElementByLink { my $self = shift; my $link = shift; my $node = $self->selectNodeByXPath ("//draw:image\[\@xlink:href=\"$link\"\]", @_); return $self->{'opendocument'} ? $node->parent : $node; } #----------------------------------------------------------------------------- # select image element list by image URL sub selectImageElementsByLink { my $self = shift; if ($self->{'opendocument'}) { my @list1 = $self->selectElementsByAttribute ('//draw:image', 'xlink:href', @_); my @list2 = (); foreach my $frame (@list1) { push @list2, $frame if $frame; } return @list2; } else { return $self->selectElementsByAttribute ($self->{'image_xpath'}, 'xlink:href', @_); } } #----------------------------------------------------------------------------- # get/set image URL sub imageLink { my $self = shift; return $self->imageAttribute(shift, 'xlink:href', @_); } #----------------------------------------------------------------------------- # return the internal filepath in canonical form ('Pictures/xxxx') sub getInternalImagePath { my $self = shift; my $image = shift; my $link = $self->imageLink($image); my $tmpl = $self->{'image_fpath'}; if ($link && ($link =~ /^$tmpl/)) { $link =~ s/^#//; return $link; } else { return undef; } } #----------------------------------------------------------------------------- # return image coordinates sub getImagePosition { my $self = shift; my $image = shift; my $element = $self->getImageElement($image); return $self->getObjectCoordinates($element, @_); } #----------------------------------------------------------------------------- # update image coordinates sub setImagePosition { my $self = shift; my $image = shift; my $element = $self->getImageElement($image); return $self->setObjectCoordinates($element, @_); } #----------------------------------------------------------------------------- # get/set image coordinates sub imagePosition { my $self = shift; my $image = shift; my $x = shift; my $y = shift; return (defined $x) ? $self->setImagePosition($image, $x, $y, @_) : $self->getImagePosition($image); } #----------------------------------------------------------------------------- # get image size sub getImageSize { my $self = shift; my $image = shift; my $element = $self->getImageElement($image); return $self->getObjectSize($element, @_); } #----------------------------------------------------------------------------- # update image size sub setImageSize { my $self = shift; my $image = shift; my $element = $self->getImageElement($image); return $self->setObjectSize($element, @_); } #----------------------------------------------------------------------------- # get/set image size sub imageSize { my $self = shift; my $image = shift; my $w = shift; my $h = shift; return (defined $w) ? $self->setImageSize($image, $w, $h, @_) : $self->getImageSize($image); } #----------------------------------------------------------------------------- # get/set image name sub imageName { my $self = shift; return $self->imageAttribute(shift, 'draw:name', @_); } #----------------------------------------------------------------------------- # get/set image stylename sub imageStyle { my $self = shift; return $self->imageAttribute(shift, 'draw:style-name', @_); } #----------------------------------------------------------------------------- # get image description sub getImageDescription { my $self = shift; my $image = shift; my $element = $self->getImageElement($image); return $self->getObjectDescription($element); } #----------------------------------------------------------------------------- # set/update image description sub setImageDescription { my $self = shift; my $image = shift; my $element = $self->getImageElement($image); return $self->setObjectDescription($element, @_); } #----------------------------------------------------------------------------- # delete image description sub removeImageDescription { my $self = shift; $self->setImageDescription(shift); } #----------------------------------------------------------------------------- # get/set accessor for image description sub imageDescription { my $self = shift; my $image = shift; my $desc = shift; return (defined $desc) ? $self->setImageDescription($image, $desc, @_) : $self->getImageDescription($image, @_); } #----------------------------------------------------------------------------- # export a selected image file from ODF container sub exportImage { my $self = shift; my $element = $self->getImageElement(shift); return undef unless $element; my $path = $self->imageLink($element) or return undef; my $tmpl = $self->{'image_fpath'}; unless ($path =~ /^$tmpl/) { warn "[" . __PACKAGE__ . "::exportImage] " . "Image content $path is an external link. " . "Can't be exported\n"; return undef; } my $target = shift; unless ($target) { my $name = $self->imageName($element); if ($name) { $path =~ /(\..*$)/; $target = $name . ($1 || ''); } else { $target = $path; } } return $self->raw_export($path, $target, @_); } #----------------------------------------------------------------------------- # export all the internal image files, or a subset of them selected by name # return the list of exported files sub exportImages { my $self = shift; my %opt = @_; my $filter = $opt{'filter'} || $opt{'name'} || $opt{'selection'}; my $basename = $opt{'path'} || $opt{'target'}; my $suffix = $opt{'suffix'} || $opt{'extension'}; my $number = defined $opt{'start_count'} ? $opt{'start_count'} : 1; my @list = (); my $count = 0; my @to_export = $filter ? $self->selectImageElementsByName($filter) : $self->getImageElementList(); my $tmpl = $self->{'image_fpath'}; IMAGE_LOOP: foreach my $image (@to_export) { my $link = $self->imageLink($image); next IMAGE_LOOP unless ($link && ($link =~ /^$tmpl/)); my $filename = undef; my $extension = undef; my $target = undef; if (defined $suffix) { $extension = $suffix; } else { $link =~ /(\..*$)/; $extension = $1 || ''; } if (defined $basename) { $target = $basename . $number . $extension; } else { my $name = $self->imageName($image) || "Image$number"; $target = $name . $extension; } $filename = $self->exportImage($image, $target); push @list, $filename if $filename; $count++; $number++; } return wantarray ? @list : $count; } #----------------------------------------------------------------------------- # import image file sub importImage { my $self = shift; my $element = $self->getImageElement(shift); return undef unless $element; my $filename = shift; my $tmpl = $self->{'image_fpath'}; unless ($filename) { my $source = $self->imageLink($element); unless ($source) { warn "[" . __PACKAGE__ . "::importImage] " . "Missing source path\n"; return undef; } $source =~ s/%(..)/{ chr(hex($1)) }/eg; $source =~ s/^\.\.[\\\/]/\.\//; $filename = $source; } my ($base, $path, $suffix) = File::Basename::fileparse($filename, '\..*'); my $link = shift; if ($link) { $link = $tmpl . $link unless $link =~ /^$tmpl/; $self->imageLink($element, $link); } else { $link = $self->imageLink($element); unless ($link && $link =~ /^$tmpl/) { $link = $tmpl . $base . $suffix; $self->imageLink($element, $link); } } $self->raw_import($link, $filename); return $link; } #----------------------------------------------------------------------------- package OpenOffice::OODoc::Element; #----------------------------------------------------------------------------- sub isImage { my $element = shift; my $name = $element->getName or return undef; if ($name eq 'draw:frame') { my $child = $element->first_child('draw:image'); return $child ? 1 : undef; } else { return ($name eq 'draw:image') ? 1 : undef; } } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/OODoc/Text.pm0000644000175000017500000034730711415446243015064 0ustar jmgjmg#---------------------------------------------------------------------------- # # $Id : Text.pm 2.243 2010-07-08 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2010 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- package OpenOffice::OODoc::Text; use 5.008_000; use strict; use OpenOffice::OODoc::XPath 2.237; our @ISA = qw ( OpenOffice::OODoc::XPath ); our $VERSION = '2.243'; #----------------------------------------------------------------------------- # synonyms BEGIN { *findElementsByContent = *selectElementsByContent; *replaceAll = *selectElementsByContent; *findTextContent = *selectTextContent; *getHeaderList = *getHeadingList; *getHeaderTextList = *getHeadingTextList; *getBibliographyElements = *getBibliographyMarks; *bibliographyElementContent = *bibliographyEntryContent; *setBibliographyElement = *setBibliographyMark; *bookmarkElement = *setBookmark; *removeBookmark = *deleteBookmark; *getHeader = *getHeading; *getHeaderContent = *getHeadingContent; *getHeaderText = *getHeadingText; *getOutlineLevel = *getLevel; *setOutlineLevel = *setLevel; *getSections = *getSectionList; *getChapter = *getChapterContent; *getParagraphContent = *getParagraphText; *createTextBox = *createTextBoxElement; *getTextBox = *getTextBoxElement; *getTextBoxElements = *getTextBoxElementList; *getList = *getItemList; *getColumn = *getTableColumn; *getRow = *getTableRow; *getHeaderRow = *getTableHeaderRow; *getCell = *getTableCell; *getSheet = *getTable; *selectTableByName = *getTableByName; *getSheetByName = *getTableByName; *getTableContent = *getTableText; *normalizeTable = *normalizeSheet; *normalizeTables = *normalizeSheets; *expandSheet = *expandTable; *insertColumn = *insertTableColumn; *deleteColumn = *deleteTableColumn; *replicateRow = *replicateTableRow; *insertRow = *insertTableRow; *appendRow = *appendTableRow; *deleteRow = *deleteTableRow; *appendHeader = *appendHeading; *insertHeader = *insertHeading; *removeHeader = *removeHeading; *deleteHeading = *removeHeading; *getNote = *getNoteElement; *getNoteList = *getNoteElementList; *getHeadingText = *getHeadingContent; *cellType = *fieldType; *cellValueAttributeName = *fieldValueAttributeName; *cellCurrency = *fieldCurrency; *getStyle = *textStyle; *setStyle = *textStyle; *removeMark = *deleteMark; *removeSpan = *removeTextStyleChanges; } #----------------------------------------------------------------------------- # default text style attributes our %DEFAULT_TEXT_STYLE = ( references => { 'style:name' => undef, 'style:family' => 'paragraph', 'style:parent-style-name' => 'Standard', 'style:next-style-name' => 'Standard', 'style:class' => 'text' }, properties => { } ); #----------------------------------------------------------------------------- # default delimiters for flat text export our %DEFAULT_DELIMITERS = ( 'text:footnote-citation' => { begin => '[', end => ']' }, 'text:note-citation' => { begin => '[', end => ']' }, 'text:footnote-body' => { begin => '{NOTE: ', end => '}' }, 'text:note-body' => { begin => '{NOTE: ', end => '}' }, 'text:span' => { begin => '<<', end => '>>' }, 'text:list-item' => { begin => '- ', end => '' }, ); #----------------------------------------------------------------------------- our $ROW_REPEAT_ATTRIBUTE = 'table:number-rows-repeated'; our $COL_REPEAT_ATTRIBUTE = 'table:number-columns-repeated'; #----------------------------------------------------------------------------- sub fieldType { my $self = shift; my $field = shift or return undef; my $newtype = shift; my $prefix = 'office'; unless ($self->{'opendocument'}) { $prefix = $field->isTableCell() ? 'table' : 'text'; } my $attribute = $prefix . ':value-type'; my $oldtype = $field->att($attribute); unless (defined $newtype) { return $oldtype; } else { if (($newtype eq 'date') || ($newtype eq 'time')) { $field->del_att($prefix . ':value'); } else { $field->del_att($prefix . ':date-value'); $field->del_att($prefix . ':time-value'); } return $field->set_att($attribute, $newtype); } } sub fieldValueAttributeName { my $self = shift; my $field = shift or return undef; my $value_type = ref $field ? $self->fieldType($field) : $field; my $attribute = ""; my $prefix = 'office'; unless ($self->{'opendocument'}) { $prefix = $field->isTableCell() ? 'table' : 'text'; } if ( ($value_type eq 'string') || ($value_type eq 'date') || ($value_type eq 'time') ) { $attribute = $prefix . ':' . $value_type . '-value'; } else { $attribute = $prefix . ':value'; } return $attribute; } #----------------------------------------------------------------------------- # constructor sub new { my $caller = shift; my $class = ref($caller) || $caller; my %options = ( level_attr => 'text:outline-level', paragraph_style => 'Standard', heading_style => 'Heading_20_1', use_delimiters => 'on', field_separator => ';', line_separator => "\n", max_rows => 32, max_cols => 26, delimiters => { %OpenOffice::OODoc::Text::DEFAULT_DELIMITERS }, @_ ); $options{heading_style} = $options{header_style} if $options{header_style}; my $object = $class->SUPER::new(%options); if ($object) { bless $object, $class; unless ($object->{'opendocument'}) { $object->{'level_attr'} = 'text:level'; $object->{'heading_style'} = 'Heading 1'; } } return $object; } #----------------------------------------------------------------------------- # getText() method adaptation for complex elements # and text output "enrichment" # (overrides getText from OODoc::XPath) sub getText { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->getElement(@_); return undef unless $element; return $self->getFlatText($element) if $element->isTextNode; return undef unless $element->isElementNode; my $text = undef; my $begin_text = ''; my $end_text = ''; my $line_break = $self->{'line_separator'} || ''; if (is_true($self->{'use_delimiters'})) { my $name = $element->getName; $begin_text = defined $self->{'delimiters'}{$name}{'begin'} ? $self->{'delimiters'}{$name}{'begin'} : ($self->{'delimiters'}{'default'}{'begin'} || ''); $end_text = defined $self->{'delimiters'}{$name}{'end'} ? $self->{'delimiters'}{$name}{'end'} : ($self->{'delimiters'}{'default'}{'end'} || ''); } $text = $begin_text; if ($element->isParagraph) { my $t = $self->SUPER::getText($element); $text .= $t if defined $t; } elsif ($element->isItemList) { return $self->getItemListText($element); } elsif ( $element->isListItem || $element->isNoteBody || $element->isTableCell || $element->isSection || $element->isTextBox ) { $element = $element->first_child('draw:text-box') if ($element->hasTag('draw:frame')); my @paragraphs = $element->children(qr '^text:(p|h)$'); while (@paragraphs) { my $p = shift @paragraphs; my $t = $self->SUPER::getText($p); $text .= $t if defined $t; $text .= $line_break if @paragraphs; } } elsif ($element->isNote) { my $b = $element->selectChildElement ('text:(|foot|end)note-body'); return $self->getText($b); } elsif ($element->isTable) { $text .= $self->getTableContent($element); } else { my $t = $self->SUPER::getText($element); $text .= $t if defined $t; } $text .= $end_text; return $text; } #----------------------------------------------------------------------------- # use or don't use delimiters for flat text output sub outputDelimitersOn { my $self = shift; $self->{'use_delimiters'} = 'on' ; } sub outputDelimitersOff { my $self = shift; $self->{'use_delimiters'} = 'off'; } sub defaultOutputTerminator { my $self = shift; my $delimiter = shift; $self->{'delimiters'}{'default'}{'end'} = $delimiter if defined $delimiter; return $self->{'delimiters'}{'default'}{'end'}; } #----------------------------------------------------------------------------- # setText() method adaptation for complex elements # overrides setText from OODoc::XPath sub setText { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $element = $self->getElement($path, $pos); return undef unless $element; return $self->SUPER::setText($element, @_) if $element->isParagraph; my $line_break = $self->{'line_separator'} || ''; if ($element->isItemList) { my @text = @_; foreach my $line (@text) { $self->appendItem($element, text => $line); } return wantarray ? @text : join $line_break, @text; } elsif ($element->isListItem) { return $self->setItemText($element, @_); } elsif ($element->isTableCell) { return $self->updateCell($element, @_); } elsif ( $element->isNoteBody || $element->isTableCell || $element->isSection ) { $element->cut_children; return $self->appendParagraph ( attachment => $element, text => shift, @_ ); } elsif ($element->isTextBox) { return $self->setTextBoxContent($element, shift); } elsif ($element->isNote) { my $b = $element->selectChildElement ('text:(|foot|end)note-body'); return $self->setText($b, @_); } else { return $self->SUPER::setText($element, @_); } } #----------------------------------------------------------------------------- # get the whole text content of the document in a readable (non-XML) form # result is a list of strings or a single string sub getTextContent { my $self = shift; return $self->selectTextContent('.*', @_); } #----------------------------------------------------------------------------- # get/set the text:id attribute of a given element sub textId { my $self = shift; return $self->identifier(@_); } #----------------------------------------------------------------------------- # selects headings, paragraph & list item elements matching a given pattern # returns a list of elements # if $action is defined, it's treated as a reference to a callback procedure # to be executed for each node matching the pattern, with the node as arg. sub selectElementsByContent { my $self = shift; my $arg1 = shift; my $pattern = undef; my $context = undef; if (ref $arg1) { $context = $arg1; $pattern = shift; } else { $context = $self->{'context'}->isa('OpenOffice::OODoc::Element') ? $self->{'context'} : $self->{'body'}; $pattern = $arg1; } my @elements = (); foreach my $node ($context->getTextDescendants) { if ( (! $pattern) || ($pattern eq '.*') || ( defined $self->_search_content ($node, $pattern, @_, $node->parent) ) ) { my $element = $node->parent or next; push @elements, $element if $element->is_elt; } } return @elements; } #----------------------------------------------------------------------------- # select the 1st element matching a given pattern sub selectElementByContent { my $self = shift; my $arg1 = shift; my $pattern = undef; my $context = undef; if (ref $arg1) { $context = $arg1; $pattern = shift; } else { $context = $self->{'context'}->isa('OpenOffice::OODoc::Element') ? $self->{'context'} : $self->{'body'}; $pattern = $arg1; } foreach my $node ($context->getTextDescendants) { if ( (! $pattern) || ($pattern eq '.*') || ( defined $self->_search_content ($node, $pattern, @_, $node->parent) ) ) { my $element = $node->parent or next; return $element if $element->is_elt; } } return undef; } #----------------------------------------------------------------------------- # selects texts matching a given pattern, with optional replacement on the fly # returns the whole text content # result is a list of strings or a single string sub selectTextContent { my $self = shift; my $pattern = shift; my $line_break = $self->{'line_separator'} || ''; my @lines = (); my $context = $self->{'context'}->isa('OpenOffice::OODoc::Element') ? $self->{'context'} : $self->{'body'}; foreach my $element ($context->getChildNodes) { next if ( (! $element->isElementNode) || ($element->isSequenceDeclarations) ); push @lines, $self->getText($element) if ( (! $pattern) || ($pattern eq '.*') || (defined $self->_search_content ($element, $pattern, @_, $element)) ); } return wantarray ? @lines : join $line_break, @lines; } #----------------------------------------------------------------------------- # get the list of text elements sub getTextElementList { my $self = shift; my $context = shift || $self->getBody(); return $self->selectChildElementsByName ( $context, qr '^t(ext:(h|p|.*list|table.*)|able:.*)$', @_ ); } #----------------------------------------------------------------------------- # get the list of paragraph elements sub getParagraphList { my $self = shift; return $self->getDescendants('text:p', @_) } #----------------------------------------------------------------------------- # get the paragraphs as a list of strings sub getParagraphTextList { my $self = shift; return $self->getTextList('//text:p', @_); } #----------------------------------------------------------------------------- # get the list of heading elements sub getHeadingList { my $self = shift; my %opt = @_; my $path = undef; unless ($opt{'level'}) { return $self->getDescendants('text:h', $opt{'context'}); } else { $path = '//text:h[@' . $self->{'level_attr'} . '="' . $opt{'level'} . '"]'; } return $self->getElementList($path, $opt{'context'}); } #----------------------------------------------------------------------------- # get the headings as a list of strings sub getHeadingTextList { my $self = shift; my @nodes = $self->getHeadingList(@_); if (wantarray) { my @list = (); foreach my $node (@nodes) { push @list, $self->getText($node); } return @list; } else { my $text = ""; my $separator = $self->{'line_separator'} || ''; foreach my $node (@nodes) { $text .= $self->getText($node); $text .= $separator; } return $text; } } #----------------------------------------------------------------------------- # get the list of span elements (i.e. text elements distinguished from their # containing paragraph by any kind of attribute such as font, color, etc) sub getSpanList { my $self = shift; return $self->getDescendants('text:span', @_); } #----------------------------------------------------------------------------- # get the span elements as a list of strings sub getSpanTextList { my $self = shift; return $self->getTextList('//text:span', @_); } #----------------------------------------------------------------------------- # set text spans that are attributed using a particular style sub setSpan { my $self = shift; my $path = shift; my $context = (ref $path) ? $path : $self->getElement($path, shift) or return undef; my $expr = $self->inputTextConversion(shift); return undef unless defined $expr; my $style = $self->inputTextConversion(shift) or return undef; return $self->markElement ($context, 'text:span', $expr, 'text:style-name' => $style); } #----------------------------------------------------------------------------- # set text spans that are attributed using a particular style sub setTextSpan { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift) or return undef; my $style = shift or return undef; my %opt = @_; my $tag = $opt{'tag'} || 'text:span'; delete $opt{'tag'}; $opt{'attributes'}{'text:style-name'} = $style; if (is_true($opt{'repeat'})) { delete $opt{'repeat'}; return $self->setChildElements($element, $tag, %opt); } else { return $self->setChildElement($element, $tag, %opt); } } #----------------------------------------------------------------------------- sub setTextSpans { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift) or return undef; my $style = shift or return undef; my %opt = @_; return $self->setTextSpan($element, $style, %opt, repeat => 'true'); } #----------------------------------------------------------------------------- sub textField { my $self = shift; my $name = shift; my %opt = ( '-prefix' => 'text', @_ ); return $self->create_field($name, %opt); } #----------------------------------------------------------------------------- sub setTextField { my $self = shift; my $path = shift; my $context = (ref $path) ? $path : $self->getElement($path, shift) or return undef; my $field_type = shift; my %opt = @_; if ($field_type eq 'variable') { $field_type = 'text:user-field-get'; $opt{'attributes'}{'text:name'} = $opt{'name'}; $opt{'attributes'}{'style:data-style-name'} = $opt{'style'}; $opt{'no_text'} = 'true'; if (is_true($opt{'check'})) { my $name = $opt{'name'} || ""; unless ($self->getUserField($name)) { warn "[" . __PACKAGE__ . "::setTextField] " . "Unknown variable $name\n"; return undef; } } delete @opt{qw(name style)}; } else { $field_type = 'text:' . $field_type unless $field_type =~ /:/; } return $self->setChildElement($context, $field_type, %opt); } #----------------------------------------------------------------------------- sub setTextFields { my $self = shift; my $path = shift; my $context = (ref $path) ? $path : $self->getElement($path, shift) or return undef; my $expr = $self->inputTextConversion(shift); my $tag = shift; my %opt = @_; if ($tag eq 'variable') { $tag = 'text:user-field-get'; $opt{'text:name'} = $opt{'name'}; if (is_true($opt{'check'})) { my $name = $opt{'name'} || ""; unless ($self->getUserField($name)) { warn "[" . __PACKAGE__ . "::setTextField] " . "Unknown variable $name\n"; return undef; } } } $opt{'style:data-style-name'} = $opt{'style'}; delete @opt{qw(name style check)}; return $self->splitContent($context, $tag, $expr, %opt); } #----------------------------------------------------------------------------- sub extendText { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $element = $self->getElement($path, $pos) or return undef; my $text = shift; return undef unless defined $text; my $style = shift; if (ref $text) { my $tagname = $text->getName; if ($tagname =~ /^text:(p|h)$/) { $text = $self->getFlatText($text); } } if ($style) { $text = $self->createElement('text:span', $text) unless ref $text; $self->textStyle($text, $style); } return $self->SUPER::extendText($element, $text, @_); } #------------------------------------------------------------------------------ # replaces substring in an element and its descendants sub replaceText { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); return $self->_search_content($element, @_); } #------------------------------------------------------------------------------ # replaces substring in an element and its descendants sub substituteText { my $self = shift; my $path = shift; my $element = (ref $path) ? $path : $self->getElement($path, shift); return undef unless $element; my $filter = $self->inputTextConversion(shift) or return undef; my $replace = shift; my %opt = @_; unless (%opt) { $replace = $self->inputTextConversion($replace) unless ref $replace; return $element->subs_text($filter, $replace); } $opt{'replace'} = $filter; if ($opt{'element'}) { my $child = $opt{'element'}; delete $opt{'element'}; return $self->setChildElement($element, $child, %opt); } my ($text_node, $start_pos, $end_pos, $match) = $self->textIndex($element, %opt); if ($text_node) { my $t = $text_node->text; substr($t, $start_pos, $end_pos - $start_pos, $replace); $text_node->set_text($t); } return undef; } #----------------------------------------------------------------------------- sub updateText { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $node = $self->getElement($path, $pos) or return undef; my %opt = @_; return undef unless @_; my $replace = $opt{'replace'}; $replace = $opt{'capture'} unless defined $replace; my $after = $opt{'after'}; my $before = $opt{'before'}; my $new_text = $opt{'text'}; $new_text = "" unless defined $new_text; $new_text = $self->inputTextConversion($new_text) unless ref $new_text; my $ln_new = ref $new_text ? 0 : length($new_text); my $offset = lc($opt{'offset'}) || 0; my $repeat = $opt{'repeat'}; delete $opt{'repeat'}; my $forward = (! defined $opt{'way'} || $opt{'way'} ne 'backward'); my $search_string = (defined $after || defined $before || defined $replace); my $nt = ref $new_text ? undef : $new_text; unless (defined $new_text && (ref $new_text || $new_text gt "")) { return undef unless defined $replace; } if ($offset eq 'start') { $nt = ref $new_text ? $self->inputTextConversion(&$new_text($self, $node)) : $new_text; $node->insertTextChild($nt, 0); $node->normalize; return 1; } elsif ($offset eq 'end') { $nt = ref $new_text ? $self->inputTextConversion(&$new_text($self, $node)) : $new_text; $node->appendTextChild($nt); $node->normalize; return 1; } my ($text_node, $start_pos, $end_pos, $match) = $self->textIndex($node, %opt); return undef unless $text_node; my $t = $text_node->text; my $p = defined $after ? $end_pos : $start_pos; my $size = defined $replace ? $end_pos - $start_pos : 0; if (ref $new_text) { $nt = $self->inputTextConversion (&$new_text($self, $text_node, $match)); $ln_new = length($nt); } substr($t, $p, $size, $nt); $text_node->set_text($t); return 1 unless is_true($repeat); my $ln_match = defined $match ? length($match) : 0; my $count = 1; $text_node = undef unless (($offset != 0) || $search_string); while ($text_node) { if ($search_string) { if ($forward) { $opt{'offset'} = $p + $ln_new; $opt{'offset'} += $ln_match if defined $before; } else { $opt{'offset'} = $p - length($t); $opt{'offset'} -= $ln_match if defined $after; } $opt{'start_mark'} = $text_node; } else { $opt{'offset'} += ($offset + $ln_new); } ($text_node, $start_pos, $end_pos, $match) = $self->textIndex($node, %opt); if ($text_node) { $ln_match = defined $match ? length($match) : 0; $t = $text_node->text; $p = defined $after ? $end_pos : $start_pos; $size = defined $replace ? $end_pos - $start_pos : 0; if (ref $new_text) { $nt = $self->inputTextConversion ( &$new_text ($self, $text_node, $match) ) || ""; $ln_new = length($nt); } substr($t, $p, $size, $nt); $text_node->set_text($t); $count++; } } return $count; } #------------------------------------------------------------------------------ sub setHyperlink { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $context = $self->getElement($path, $pos) or return undef; my $expr = shift; return undef unless defined $expr; my $url = shift or return undef; my %opt = @_; my $tag = 'text:a'; $opt{'attributes'}{'xlink:href'} = $url; $opt{'attributes'}{'xlink:type'} = 'simple' unless $opt{'attributes'}{'xlink:type'}; $opt{'attributes'}{'office:name'} = $opt{'name'}; delete @opt{qw(name before after content)}; $opt{'capture'} = $expr; return $self->setChildElement($context, $tag, %opt); } #----------------------------------------------------------------------------- sub setHyperlinks { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $context = $self->getElement($path, $pos) or return undef; my $expr = shift; return undef unless defined $expr; my $url = shift or return undef; my %opt = ( 'xlink:href' => $url, 'xlink:type' => 'simple', @_ ); return $self->markElement($context, 'text:a', $expr, %opt); } #----------------------------------------------------------------------------- sub selectHyperlinkElements { my $self = shift; my $url = shift; return $self->selectElementsByAttribute ('//text:a', 'xlink:href', $url, @_); } #----------------------------------------------------------------------------- sub selectHyperlinkElement { my $self = shift; my $url = shift; return $self->selectElementByAttribute ('//text:a', 'xlink:href', $url, @_); } #----------------------------------------------------------------------------- sub hyperlinkURL { my $self = shift; my $hl = shift or return undef; unless (ref $hl) { $hl = $self->selectHyperlinkElement($hl); return undef unless $hl; } my $url = shift; if ($url) { $self->setAttribute($hl, 'xlink:href', $url); } return $self->getAttribute($hl, 'xlink:href'); } #----------------------------------------------------------------------------- sub setAnnotation { my $self = shift; my $path = shift; my $pos = ref $path ? undef : shift; my $element = $self->getElement($path, $pos); my %opt = @_; my $text = $opt{'text'}; delete $opt{'text'}; my $style = $opt{'style'}; delete $opt{'style'}; my $creator = $opt{'creator'} || $opt{'author'} || $ENV{'USER'}; delete $opt{'author'}; delete $opt{'creator'}; my $date = (defined $opt{'date'}) ? $opt{'date'} : odfLocaltime(); delete $opt{'date'}; delete $opt{'capture'}; my $annotation = $self->setChildElement ($element, 'office:annotation', %opt); $self->appendElement ($annotation, 'dc:creator', text => $creator); $self->appendElement ($annotation, 'dc:date', text => $date); $self->appendParagraph ( attachment => $annotation, text => $text, style => $style ); return $annotation; } #----------------------------------------------------------------------------- # creates and inserts a footnote or endnote sub setNote { my $self = shift; my $path = shift; my $pos = ref $path ? undef : shift; my $element = $self->getElement($path, $pos) or return undef; my %opt = ( 'style' => 'Standard', 'citation' => undef, 'id' => undef, 'class' => 'footnote', 'label' => undef, @_ ); my $text = $opt{'text'}; delete $opt{'text'}; my $note = $self->setChildElement($element, 'text:note', %opt); $self->setAttributes ( $note, 'text:id' => $opt{'id'}, 'text:note-class' => $opt{'class'} ); my $note_citation = $note->appendChild('text:note-citation'); if (defined $opt{'label'}) { $self->setAttribute ($note_citation, 'text:label', $opt{'label'}); $opt{'citation'} = $opt{'label'} unless defined $opt{'citation'}; } $self->setText($note_citation, $opt{'citation'}); my $note_body = $note->appendChild('text:note-body'); $self->appendParagraph ( attachment => $note_body, text => $text, style => $opt{'style'} ); return $note; } #----------------------------------------------------------------------------- sub removeTextStyleChanges { my $self = shift; my $path = shift or return undef; my $context = ref $path ? $path : $self->getElement($path, @_); return undef unless $context; my $span_name = 'text:span'; my $name = $context->getName; unless ($name =~ /^text:(p|h|span)$/) { warn "[" . __PACKAGE__ . "::removeTextStyleChanges] " . "$name is not a text container\n"; return undef; } my $new_elt = OpenOffice::OODoc::Element->new($name); $new_elt->set_atts($context->atts); my $count = 0; foreach my $n ($context->descendants) { if ($n->getName() ne $span_name) { $n->move(last_child => $new_elt); } else { $count++; } } if ($count > 0) { $new_elt->replace($context); return $new_elt; } else { $new_elt->delete; return $context; } } #----------------------------------------------------------------------------- sub removeHyperlink { my $self = shift; return $self->removeSpan(@_, 'text:a'); } #----------------------------------------------------------------------------- # get all the bibliographic entries sub getBibliographyMarks { my $self = shift; my $id = shift; unless ($id) { return $self->getDescendants('text:bibliography-mark'); } else { return $self->selectElementsByAttribute ( '//text:bibliography-mark', 'text:identifier', $id, @_ ); } } #----------------------------------------------------------------------------- # get/set the content of a bibliography entry sub bibliographyEntryContent { my $self = shift; my $id = shift; my $e = undef; my %desc = @_; unless (ref $id) { my $i = $self->inputTextConversion($id); $e = $self->getNodeByXPath ( "//text:bibliography-mark[\@text:identifier=\"$i\"]", $desc{'context'} ); } else { $e = $id; } return undef unless $e; my $k = undef; foreach $k (keys %desc) { next if $k =~ /:/; my $v = $desc{$k}; delete $desc{$k}; $k = 'text:' . $k; $desc{$k} = $v; } $self->setAttributes($e, %desc); %desc = $self->getAttributes($e); foreach $k (keys %desc) { my $new_key = $k; $new_key =~ s/^text://; my $v = $desc{$k}; delete $desc{$k}; $desc{$new_key} = $v; } return %desc; } #----------------------------------------------------------------------------- # inserts a new bibliography entry within a text element sub setBibliographyMark { my $self = shift; my $path = shift; my $pos = ref $path ? undef : shift; my $element = $self->getElement($path, $pos); my %opt = @_; my $bib = $self->setChildElement( $element, 'bibliography-mark', @_ ); # my $bib = $self->createElement('text:bibliography-mark'); $self->bibliographyEntryContent($bib, @_); return $bib; } #----------------------------------------------------------------------------- # creates a pair of markup elements as range delimiters sub setRangeMark { my $self = shift; my $type = shift or return undef; my $id = shift or return undef; my %opt = @_; $type =~ s/ /-/g; my $check = $opt{'check'}; delete $opt{'check'}; my $prefix = $opt{'prefix'} || 'text'; my $context = $opt{'context'}; delete $opt{'context'}; my $content = $opt{'content'}; delete @opt{qw(after before replace)}; my %start = (); my %end = (); my %attributes = (); if ($opt{'start'}) { %start = %{$opt{'start'}}; delete $opt{'start'}; } if ($opt{'end'}) { %end = %{$opt{'end'}}; delete $opt{'end'}; } delete $start{'attributes'}; delete $end{'attributes'}; $end{'context'} = $start{'context'} unless $end{'context'}; if ($opt{'attributes'}) { %attributes = %{$opt{'attributes'}}; delete $opt{'attributes'}; } $type = "$prefix:$type" unless $type =~ /:/; my $start_tag = $type . '-start'; my $end_tag = $type . '-end'; my $start_context = $context || $start{'context'}; my $end_context = $context || $end{'context'} || $start_context; delete $start{'context'}; delete $end{'context'}; my $start_mark = undef; my $end_mark = undef; $opt{'no_text'} = 'true'; if (defined $content) { delete @opt{qw(before after replace content)}; $opt{'no_text'} = 'true'; $end_mark = $self->setChildElement ( $context, $end_tag, %opt, after => $content ); $start_mark = $self->setChildElement ( $context, $start_tag, %opt, before => $content ); } else { $end_mark = $self->setChildElement ( $end_context, $end_tag, %end, no_text => 'true' ); $start_mark = $self->setChildElement ( $start_context, $start_tag, %start, no_text => 'true' ); } unless ($start_mark && $end_mark) { $self->removeElement($start_mark); $self->removeElement($end_mark); return wantarray ? (undef, undef) : undef; } elsif (is_true($check)) { if ($end_mark->before($start_mark)) { warn "[" . __PACKAGE__ . "::setRangeMark] " . "End position before start position\n"; $start_mark->delete; $end_mark->delete; return wantarray ? (undef, undef) : undef; } } unless ($type =~ /bookmark/) { $self->setIdentifier($start_mark, $id); $self->setIdentifier($end_mark, $id); } else { $self->elementName($start_mark, $id); $self->elementName($end_mark, $id); } $self->setAttributes($start_mark, %attributes); return wantarray ? ($start_mark, $end_mark) : $start_mark; } #------------------------------------------------------------------------------ sub checkRangeMark { my $self = shift; my $id = shift; my $type = shift; my $context = shift; $type =~ s/ /-/g; $type = 'text:' . $type unless $type =~ /:/; my $attr = ($type =~ /bookmark/) ? 'text:name' : 'text:id'; my $start_tag = $type . '-start'; my $end_tag = $type . '-end'; my $start = $self->selectNodeByXPath ("//$start_tag\[\@$attr=\"$id\"\]", $context); my $end = $self->selectNodeByXPath ("//$end_tag\[\@$attr=\"$id\"\]", $context); if ($start && $end) { return $start->before($end) ? TRUE : FALSE; } elsif ($start || $end) { return FALSE; } return undef; } #------------------------------------------------------------------------------ sub deleteMark { my $self = shift; my $id = $self->inputTextConversion(shift); my $type = shift; my $attr = shift || 'text:id'; my $context = shift; $attr =~ s/ /-/g; $attr = 'text:' . $attr unless $attr =~ /:/; $type =~ s/ /-/g; $type = 'text:' . $type unless $type =~ /:/; my $start_tag = $type . '-start'; my $end_tag = $type . '-end'; my $count = 0; foreach my $e ( $self->getElementList ("//$type\[\@$attr=\"$id\"\]", $context), $self->getElementList ("//$start_tag\[\@$attr=\"$id\"\]", $context), $self->getElementList ("//$end_tag\[\@$attr=\"$id\"\]", $context) ) { $e->delete; $count++; } return $count; } #------------------------------------------------------------------------------ sub deleteMarks { my $self = shift; my $type = shift; my $context = shift; $type =~ s/ /-/g; $type = 'text:' . $type unless $type =~ /:/; my $start_tag = $type . '-start'; my $end_tag = $type . '-end'; my $count = 0; foreach my $e ( $self->getElementList("//$type", $context), $self->getElementList("//$start_tag", $context), $self->getElementList("//$end_tag", $context) ) { $e->delete; $count++; } return $count; } #----------------------------------------------------------------------------- # get a bookmark sub getBookmark { my $self = shift; my $name = shift or return undef; unless (ref $name) { return ( $self->getNodeByXPath ("//text:bookmark\[\@text:name=\"$name\"\]", @_) || $self->getNodeByXPath ("//text:bookmark-start\[\@text:name=\"$name\"\]", @_) ); } else { my $tag = $name->getName; return ($tag =~ /^text:bookmark/) ? $name : undef; } } #----------------------------------------------------------------------------- # retrieve the element where is a given bookmark sub selectElementByBookmark { my $self = shift; my $bookmark = $self->getBookmark(@_); return $bookmark ? $bookmark->parent : undef; } #----------------------------------------------------------------------------- sub setRangeBookmark { my $self = shift; return $self->setRangeMark('text:bookmark', @_); } #----------------------------------------------------------------------------- # set a position or range bookmark sub setBookmark { my $self = shift; my $context = undef; my $name = undef; my $arg1 = shift or return undef; if (ref $arg1) { $context = $arg1; $name = shift or return undef; } else { $name = $arg1; } my %opt = @_; delete $opt{'text'}; # no text content for bookmarks if (defined $context) # one target element => position bookmark { delete $opt{'context'}; $opt{'attributes'}{'text:name'} = $name; $opt{'no_text'} = 'true'; return $self->setChildElement($context, 'text:bookmark', %opt); } else # else => range bookmark { return $self->setRangeBookmark($name, %opt); } } #----------------------------------------------------------------------------- # check the existence and consistency of a range bookmark sub checkRangeBookmark { my $self = shift; my $name = shift; return $self->checkRangeMark($name, 'bookmark', @_); } #----------------------------------------------------------------------------- # delete a bookmark sub deleteBookmark { my $self = shift; my $name = shift; return $self->deleteMark($name, 'text:bookmark', 'text:name', @_); } #----------------------------------------------------------------------------- # delete all the bookmarks in the context sub deleteBookmarks { my $self = shift; return $self->deleteMarks('text:bookmark', @_); } #----------------------------------------------------------------------------- # creates an alphabetical index or TOC mark sub setIndexMark { my $self = shift; my $path = shift; my $pos = ref $path ? undef : shift; my $element = $self->getElement($path, $pos) or return undef; my $id = shift or return undef; my %opt = @_; my $type = $opt{'type'} || 'alphabetical-index'; delete $opt{'type'}; $opt{'context'} = $element; my $tag = 'text:' . $type . '-mark'; return $self->setRangeMark($tag, $id, %opt); } #----------------------------------------------------------------------------- # check the existence and consistency of a range bookmark sub checkIndexMark { my $self = shift; my $id = shift; my $type = shift; $type = $type . '-mark'; return $self->checkRangeMark($id, $type, @_); } #----------------------------------------------------------------------------- # delete an index mark sub deleteIndexMark { my $self = shift; my $id = shift; my $type = shift; $type = $type . '-mark'; return $self->deleteMark($id, $type, 'text:id', @_); } #----------------------------------------------------------------------------- # delete all the index marks of a given type in the context sub deleteIndexMarks { my $self = shift; my $type = shift; if ($type) { $type = $type . '-mark'; return $self->deleteMarks($type, @_); } else { return $self->deleteMarks('text:toc-mark', @_) + $self->deleteMarks('text:alphabetical-index-mark', @_); } } #----------------------------------------------------------------------------- # get the footnote bodies in the document sub getFootnoteList { my $self = shift; my $xpath = $self->{'opendocument'} ? '//text:note[@text:note-class="footnote"]/text:note-body' : '//text:footnote-body'; return $self->getElementList($xpath, @_); } #----------------------------------------------------------------------------- # get the footnote citations in the document sub getFootnoteCitationList { my $self = shift; my $xpath = $self->{'opendocument'} ? '//text:note[@text:note-class="footnote"]/text:note-citation' : '//text:footnote-citation'; return $self->getElementList($xpath, @_); } #----------------------------------------------------------------------------- # get the endnote bodies in the document sub getEndnoteList { my $self = shift; my $xpath = $self->{'opendocument'} ? '//text:note[@text:note-class="endnote"]/text:note-body' : '//text:endnote-body'; return $self->getElementList($xpath, @_); } #----------------------------------------------------------------------------- # get the endnote citations in the document sub getEndnoteCitationList { my $self = shift; my $xpath = $self->{'opendocument'} ? '//text:note[@text:note-class="endnote"]/text:note-citation' : '//text:endnote-citation'; return $self->getElementList($xpath, @_); } #----------------------------------------------------------------------------- # get the note citations in the document (ODF only) sub getNoteCitationList { my $self = shift; return $self->getDescendants('text:note-citation', @_); } #----------------------------------------------------------------------------- sub getNoteElementList { my $self = shift; my $class = shift; unless ($class) { if ($self->{'opendocument'}) { return $self->getElementList('//text:note'); } else { return ( $self->getElementList('//text:footnote'), $self->getElementList('//text:endnote') ); } } elsif (($class eq 'footnote') or ($class eq 'endnote')) { if ($self->{'opendocument'}) { return $self->getElementList ("//text:note\[\@text:note-class=\"$class\"\]"); } else { return $self->getElementList("//text:$class"); } } else { warn "[" . __PACKAGE__ . "::getNoteElementList] " . "Unknown note class $class\n"; return undef; } } #----------------------------------------------------------------------------- # retrieve a note element using its identifier (ODF only) sub getNoteElement { my $self = shift; my $p1 = shift; if (ref $p1) { return $p1->isNote ? $p1 : undef; } else { unshift @_, $p1; } my %opt = @_; my $xpath = undef; my $id = $opt{id}; my $class = $opt{class}; my $citation = $opt{citation}; if ($id) { unless ($self->{'opendocument'}) { return $self->getElement ("//text:$class\[\@text:id=\"$id\"\]") if $class; return $self->getElement ("//text:footnote\[\@text:id=\"$id\"\]") || $self->getElement ("//text:endnote\[\@text:id=\"$id\"\]"); } else { my $xpath = $class ? "//text:note\[\@text:note-class=\"$class\"" . " and \@text:id=\"$id\"\]" : "//text:note\[\@text:id=\"$id\"\]"; return $self->getElement($xpath); } } elsif ($class && defined $citation) { my @list = $self->getNoteElementList($class); my $tagname = $self->{'opendocument'} ? "text:note-citation" : "text:$class-citation"; foreach my $elt (@list) { next unless $elt; my $text = $self->getFlatText ($elt->first_child($tagname)); return $elt if $text eq $citation; } return undef; } else { warn "[" . __PACKAGE__ . "::getNoteElement] " . "Requires (Id) OR (class AND citation)\n"; return undef; } } #----------------------------------------------------------------------------- sub getNoteClass { my $self = shift; my $element = shift or return undef; unless (ref $element) { unshift @_, $element; $element = $self->getNoteElement(@_) or return undef; } return $element->getNoteClass; } #----------------------------------------------------------------------------- # get the list of tables in the document sub getTableList { my $self = shift; return $self->getElementList('//table:table', @_); } #----------------------------------------------------------------------------- # get a heading element selected by position number and level sub getHeading { my $self = shift; my $pos = shift; my %opt = (@_); my $heading = undef; if (ref $pos) { return undef unless $pos->isHeading; if ($opt{'level'}) { my $level = $pos->att($self->{'level_attr'}); return undef unless ($level && ($level == $opt{'level'})); } return $pos; } unless ($opt{'level'}) { $heading = $self->getElement ('//text:h', $pos, $opt{'context'}); } else { my $path = '//text:h[@' . $self->{'level_attr'} . '="' . $opt{'level'} . '"]'; $heading = $self->getElement ($path, $pos, $opt{'context'}); } return undef unless $heading; } #----------------------------------------------------------------------------- # get the text of a heading element sub getHeadingContent { my $self = shift; return $self->getText('//text:h', @_); } #----------------------------------------------------------------------------- # get the level attribute (if defined) of an element # the level must be defined for heading elements sub getLevel { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $element = $self->getElement($path, $pos, @_); return $element->getAttribute($self->{'level_attr'}) || ""; } #----------------------------------------------------------------------------- sub setLevel { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $level = shift; my $element = $self->getElement($path, $pos, @_) or return undef; return $element->setAttribute($self->{'level_attr'} => $level); } #----------------------------------------------------------------------------- sub makeHeading { my $self = shift; my %opt = @_; my $element = $opt{'element'}; if ($element) { $element->set_name('text:h'); } else { $element = $self->createElement('text:h'); } if ($opt{'level'}) { $element->set_att($self->{'level_attr'}, $opt{'level'}); } my $style = $opt{'style'} ? $opt{'style'} : $self->{'heading_style'}; $self->setAttribute($element, 'text:style-name', $style); if (defined $opt{'text'}) { $self->setText($element, $opt{'text'}); } return $element; } #----------------------------------------------------------------------------- sub getSection { my $self = shift; my $name = shift; return undef unless defined $name; if (ref $name) { return ($name->isSection) ? $name : undef; } if (($name =~ /^\d*$/) || ($name =~ /^[\d+-]\d+$/)) { return $self->getElement('//text:section', $name, @_); } my $n = $self->inputTextConversion($name); return $self->selectElementByAttribute ('text:section', 'text:name', $n, @_); } #----------------------------------------------------------------------------- sub getSectionList { my $self = shift; return $self->getDescendants('text:section', @_); } #----------------------------------------------------------------------------- sub sectionStyle { my $self = shift; my $section = $self->getSection(shift) or return undef; my $new_style = shift; return $new_style ? $self->setAttribute($section, 'text:style-name', $new_style) : $self->getAttribute($section, 'text:style-name'); } #----------------------------------------------------------------------------- sub renameSection { my $self = shift; my $section = $self->getSection(shift) or return undef; my $newname = shift or return undef; if ($self->getSection($newname)) { warn "[" . __PACKAGE__ . "::renameSection] " . "Section name $newname already in use\n"; return undef; } return $self->setAttribute($section, 'text:name' => $newname); } #----------------------------------------------------------------------------- sub sectionName { my $self = shift; my $section = $self->getSection(shift) or return undef; my $newname = shift; return $newname ? $self->renameSection($section, $newname) : $self->getAttribute($section, 'text:name'); } #----------------------------------------------------------------------------- sub appendSection { my $self = shift; my $name = shift; my %opt = ( 'attachment' => $self->{'body'}, 'style' => $name, 'protected' => 'false', @_ ); if ($self->getSection($name, $self->{'xpath'})) { warn "[" . __PACKAGE__ . "::appendSection] " . "Section $name exists\n"; return undef; } my $link = undef; if ($opt{"link"}) { $link = $opt{'link'}; delete $opt{'link'} } my $section = $self->appendElement ( $opt{'attachment'}, 'text:section', attribute => { 'text:name' => $name, 'text:style-name' => $opt{'style'} }, %opt ) or return undef; $self->insertSubdocument ($section, $link, $opt{'filter'}) if $link; $section->set_att('text:protected', $opt{'protected'}) if $opt{'protected'}; $section->set_att('text:protection-key', $opt{'key'}) if $opt{'key'}; return $section; } #----------------------------------------------------------------------------- sub lockSection { my $self = shift; my $section = $self->getSection(shift) or return undef; $section->set_att('text:protected', 'true'); my $key = shift; $section->set_att('text:protection-key', $key) if $key; } sub unlockSection { my $self = shift; my $section = $self->getSection(shift) or return undef; $section->del_att('text:protected'); my $key = $section->att('text:protection-key'); $section->del_att('text:protection-key'); return $key; } sub unlockSections { my $self = shift; foreach my $section ($self->getSectionList(@_)) { $self->unlockSection($section); } } sub sectionProtectionKey { my $self = shift; my $section = $self->getSection(shift) or return undef; return $section->att('text:protection-key'); } #----------------------------------------------------------------------------- sub insertSection { my $self = shift; my $path = shift; my $pos = ref $path ? undef : shift; my $name = shift; my %opt = ( 'style' => $name, 'protected' => 'false', @_ ); my $posnode = $self->getElement($path, $pos, $opt{'context'}) or return undef; if ($self->getSection($name, $self->{'xpath'})) { warn "[" . __PACKAGE__ . "::insertSection] " . "Section $name exists\n"; return undef; } my $link = undef; if ($opt{"link"}) { $link = $opt{'link'}; delete $opt{'link'} } my $section = $self->insertElement ( $posnode, 'text:section', attribute => { 'text:name' => $name, 'text:style-name' => $opt{'style'} }, %opt ) or return undef; $self->insertSubdocument ($section, $link, $opt{'filter'}) if $link; $section->set_att('text:protected', $opt{'protected'}) if $opt{'protected'}; $section->set_att('text:protection-key', $opt{'key'}) if $opt{'key'}; return $section; } #----------------------------------------------------------------------------- # link a section to a subdocument our $section_source_tag = "text:section-source"; sub insertSubdocument { my $self = shift; my $section_id = shift; my $url = shift; my %attr = (); my $section = $self->getSection($section_id); unless ($section) { warn "[" . __PACKAGE__ . "::insertSubdocument] " . "Non existing target section\n"; return undef; } my $doclink = $section->first_child($section_source_tag) || $self->appendElement($section, $section_source_tag); if ($attr{'filter'}) { $attr{'text:filter-name'} = $attr{'filter'}; delete $attr{'filter'}; } $self->setAttributes($doclink, "xlink:href" => $url, %attr); return $doclink; } #----------------------------------------------------------------------------- # get the content depending on a given heading element sub getChapterContent { my $self = shift; my $h = shift || 0; my $heading = ref $h ? $h : $self->getHeading($h, @_); return undef unless $heading; my @list = (); my $level = $self->getLevel($heading) or return @list; my $next_element = $heading->next_sibling; while ($next_element) { my $l = $self->getLevel($next_element); last if ($l && $l <= $level); push @list, $next_element; $next_element = $next_element->next_sibling; } return @list; } #----------------------------------------------------------------------------- sub moveElementsToSection { my $self = shift; my $section = $self->getSection(shift) or return undef; $section->pickUpChildren(@_); return $section; } #----------------------------------------------------------------------------- # get a paragraph element selected by number sub getParagraph { my $self = shift; return $self->getElement('//text:p', @_); } #----------------------------------------------------------------------------- # same as getParagraph() but only among the 1st level paragraphs # and only in text documents sub getTopParagraph { my $self = shift; my $path = $self->{'opendocument'} ? '//office:body/office:text/text:p' : '//office:body/text:p'; return $self->getElement($path, @_); } #----------------------------------------------------------------------------- # select paragraphs by stylename sub selectParagraphsByStyle { my $self = shift; return $self->selectElementsByAttribute ('//text:p', 'text:style-name', @_); } #----------------------------------------------------------------------------- # select a single paragraph by stylename sub selectParagraphByStyle { my $self = shift; return $self->selectElementByAttribute ('//text:p', 'text:style-name', @_); } #----------------------------------------------------------------------------- # get text content of a paragraph sub getParagraphText { my $self = shift; return $self->getText('//text:p', @_); } #----------------------------------------------------------------------------- # select a draw page by name sub selectDrawPageByName { my $self = shift; my $text = $self->inputTextConversion(shift); return $self->selectNodeByXPath ("//draw:page\[\@draw:name=\"$text\"\]", @_); } #----------------------------------------------------------------------------- # get a draw page by position or name sub getDrawPage { my $self = shift; my $p = shift; return undef unless defined $p; if (ref $p) { return ($p->isDrawPage) ? $p : undef; } if ($p =~ /^[\-0-9]*$/) { return $self->getElement('//draw:page', $p, @_); } else { return $self->selectDrawPageByName($p, @_); } } #----------------------------------------------------------------------------- sub getDrawPages { my $self = shift; return $self->getDescendants('draw:page', @_); } #----------------------------------------------------------------------------- # create a draw page (to be inserted later) sub createDrawPage { my $self = shift; my $class = $self->contentClass; unless ($class eq 'presentation' || $class eq 'drawing') { warn "[" . __PACKAGE__ . "::createDrawPage] " . "Unsupported operation for this document\n"; return undef; } my %opt = @_; my $body = $self->getBody; my $p = $self->createElement('draw:page'); $self->setAttribute($p, 'draw:name' => $opt{'name'}) if $opt{'name'}; $self->setAttribute($p, 'draw:id' => $opt{'id'}) if $opt{'id'}; $self->setAttribute($p, 'draw:style-name' => $opt{'style'}) if $opt{'style'}; $self->setAttribute($p, 'draw:master-page-name' => $opt{'master'}) if $opt{'master'}; return $p; } #----------------------------------------------------------------------------- # append a new draw page to the document sub appendDrawPage { my $self = shift; my $page = $self->createDrawPage(@_) or return undef; my $body = $self->getBody; $self->appendElement($body, $page); return $page; } #----------------------------------------------------------------------------- # insert a new draw page before or after an existing one sub insertDrawPage { my $self = shift; my $pos = shift or return undef; my $pos_page = $self->getDrawPage($pos); unless ($pos_page) { warn "[" . __PACKAGE__ . "::insertDrawPage] " . "Unknown position\n"; return undef; } my %opt = @_; my $page = $self->createDrawPage(%opt) or return undef; $self->insertElement($pos_page, $page, position => $opt{'position'}); return $page; } #----------------------------------------------------------------------------- sub drawPageAttribute { my $self = shift; my $att = shift; my $pos = shift; my $page = $self->getDrawPage($pos) or return undef; my $value = shift; return $value ? $self->setAttribute($page, $att, $value) : $self->getAttribute($page, $att); } #----------------------------------------------------------------------------- sub drawPageName { my $self = shift; return $self->drawPageAttribute('draw:name', @_); } #----------------------------------------------------------------------------- sub drawPageStyle { my $self = shift; return $self->drawPageAttribute('draw:style-name', @_); } #----------------------------------------------------------------------------- sub drawPageId { my $self = shift; return $self->drawPageAttribute('draw:id', @_); } #----------------------------------------------------------------------------- sub drawMasterPage { my $self = shift; return $self->drawPageAttribute('draw:master-page-name', @_); } #----------------------------------------------------------------------------- sub createTextBoxElement { my $self = shift; my %opt = @_; my $frame = undef; my $text_box = undef; if ($self->{'opendocument'}) { $frame = $self->createFrame(tag => 'draw:frame', %opt); $text_box = $self->appendElement($frame, 'draw:text-box'); } else { $text_box = $self->createFrame(tag => 'draw:text-box', %opt); $frame = $text_box; } if ($opt{'content'}) { if (ref $opt{'content'}) { $opt{'content'}->paste_last_child($text_box); } else { $self->appendParagraph ( attachment => $text_box, text => $opt{'content'} ); } } return wantarray ? ($frame, $text_box) : $text_box; } #----------------------------------------------------------------------------- sub getTextBoxElement { my $self = shift; my $tb = shift; return undef unless defined $tb; if (ref $tb) { my $name = $tb->getName; if ($name eq 'draw:frame') { return $tb->first_child('draw:text-box') ? $tb : undef; } elsif ($name eq 'draw:text-box') { return $tb unless $self->{'opendocument'}; my $frame = $tb->parent; return $frame->isFrame ? $frame : undef; } else { return undef; } } else { if ($tb =~ /^[\-0-9]*$/) { my $e = $self->getElement('//draw:text-box', $tb, @_); return $self->{'opendocument'} ? $e->parent() : $e; } else { return $self->selectTextBoxElementByName($tb, @_); } } } #----------------------------------------------------------------------------- sub setTextBoxContent { my $self = shift; my $frame = $self->getTextBoxElement(shift) or return undef; if ($frame->isFrame) { $frame = $frame->first_child('draw:text-box') or return undef; } $frame->cut_children; my $content = shift; if (ref $content) { $content->paste_last_child($frame); return $content; } else { return $self->appendParagraph ( attachment => $frame, text => $content ); } } #----------------------------------------------------------------------------- # text box attributes accessors sub textBoxCoordinates { my $self = shift; my $tb = $self->getTextBoxElement(shift) or return undef; my $coord = shift; return (defined $coord) ? $self->setObjectCoordinates($tb, $coord) : $self->getObjectCoordinates($tb); } sub textBoxSize { my $self = shift; my $tb = $self->getTextBoxElement(shift) or return undef; my $size = shift; return (defined $size) ? $self->setObjectSize($tb, $size) : $self->getObjectSize($tb); } sub textBoxDescription { my $self = shift; my $tb = $self->getTextBoxElement(shift) or return undef; my $description = shift; return (defined $description) ? $self->setObjectDescription($tb, $description) : $self->getObjectDescription($tb); } sub textBoxName { my $self = shift; my $tb = $self->getTextBoxElement(shift) or return undef; return $self->objectName($tb, shift); } #----------------------------------------------------------------------------- sub selectTextBoxElementByName { my $self = shift; my $tag = $self->{'opendocument'} ? 'draw:frame' : 'draw:text-box'; my $frame = $self->getFrameElement(shift, $tag); if ($self->{'opendocument'}) { return undef unless ($frame->first_child('draw:text-box')); } return $frame; } #----------------------------------------------------------------------------- sub getTextElementist { my $self = shift; my $context = shift; my @tblist = $self->getDescendants('draw:text-box', $context); return @tblist unless $self->{'opendocumpent'}; my @frlist = (); foreach my $tb (@tblist) { push @frlist, $tb->parent; } return @frlist; } #----------------------------------------------------------------------------- # get list element sub getItemList { my $self = shift; my $pos = shift; if (ref $pos) { return $pos->isItemList ? $pos : undef; } return $self->getElement('//text:list', $pos, @_); } #----------------------------------------------------------------------------- # return the text content of an item list (in array or string) sub getItemListText { my $self = shift; my $list = $self->getItemList(@_) or return undef; my @items = $list->children('text:list-item'); if (wantarray) { my @result = (); foreach my $item (@items) { push @result, $self->getItemText($item); } return @result; } else { my $tagname = $list->getName; my $line_break = $self->{'line_separator'} || ''; my $item_begin = $self->{'delimiters'}{'text:p'}{'begin'} || ''; my $item_end = $self->{'delimiters'}{'text:p'}{'end'} || ''; my $result = $self->{'delimiters'}{$tagname}{'begin'} || ''; my $end_list = $self->{'delimiters'}{$tagname}{'end'} || ''; my $count = 0; foreach my $item (@items) { $result .= $line_break if $count > 0; $result .= $item_begin; $result .= ($self->getItemText($item) || ""); $result .= $item_end; $count++; } $result .= $end_list; return $result; } } #----------------------------------------------------------------------------- # get ordered list root element sub getOrderedList { my $self = shift; my $pos = shift; if (ref $pos) { return $pos->isOrderedList ? $pos : undef; } return $self->getElement('//text:ordered-list', $pos, @_); } #----------------------------------------------------------------------------- # get unordered list root element sub getUnorderedList { my $self = shift; my $pos = shift; if (ref $pos) { return $pos->isUnorderedList ? $pos : undef; } return $self->getElement('//text:unordered-list', $pos, @_); } #----------------------------------------------------------------------------- # get item elements list sub getItemElementList { my $self = shift; my $list = shift; return $list->children('text:list-item'); } #----------------------------------------------------------------------------- sub getListItem { my $self = shift; my $list = $self->getItemList(shift) or return undef; return $list->child(shift, 'text:list-item'); } #----------------------------------------------------------------------------- # get item element text sub getItemText { my $self = shift; my $item = shift; return undef unless $item; my $para = $item->selectChildElement('text:(p|h)'); return $para ? $self->getText($para) : undef; } #----------------------------------------------------------------------------- # set item element text sub setItemText { my $self = shift; my $item = shift; return undef unless $item; my $text = shift; return undef unless (defined $text); my $para = $item->selectChildElement('text:(p|h)') || $self->appendElement($item, 'text:p'); return $self->setText($para, $text); } #----------------------------------------------------------------------------- # get item element style sub getItemStyle { my $self = shift; my $item = shift; return undef unless $item; my $para = $item->selectChildElement('text:(p|h)'); return $self->textStyle($para); } #----------------------------------------------------------------------------- # set item element style sub setItemStyle { my $self = shift; my $item = shift; return undef unless $item; my $style = shift; my $para = $item->selectChildElement('text:(p|h)'); return $self->textStyle($para, $style); } #----------------------------------------------------------------------------- # append a new item in a list sub appendListItem { my $self = shift; my $list = shift; return undef unless $list; my %opt = ( type => 'text:p', @_ ); my $type = $opt{'type'}; my $item = $self->appendElement($list, 'text:list-item'); return $item unless $type; my $text = $opt{'text'}; my $style = $opt{'style'}; $style = $opt{'attribute'}{'text:style-name'} unless $style; unless ($style) { my $first_item = $list->selectChildElement('text:list-item'); if ($first_item) { my $p = $first_item->selectChildElement ('text:(p|h)'); $style = $self->textStyle($p) if ($p); } } if ($type eq 'paragraph') { $type = 'text:p'; } elsif ($type eq 'heading') { $type = 'text:h'; } my $para = $self->appendElement ( $item, $type, text => $text ); $style = $self->{'paragraph_style'} unless $style; $opt{'attribute'}{'text:style-name'} = $style; $self->setAttributes($para, %{$opt{'attribute'}}); return $item; } sub appendItem { my $self = shift; return $self->appendListItem(@_); } #----------------------------------------------------------------------------- # append a new item list sub appendItemList { my $self = shift; my %opt = @_; my $name = 'text:unordered-list'; $opt{'attribute'}{'text:style-name'} = $opt{'style'} if $opt{'style'}; $opt{'attribute'}{'text:style-name'} = $self->{'paragraph_style'} unless $opt{'attribute'}{'text:style-name'}; $opt{'attribute'}{'text:continue-numbering'} = $opt{'continue-numbering'} if $opt{'continue-numbering'}; if ($self->{'opendocument'}) { $name = 'text:list'; } else { if (defined $opt{'type'} && ($opt{'type'} eq 'ordered')) { $name = 'text:ordered-list' ; } } my $attachment = $opt{'attachment'} || $self->{'body'}; return $self->appendElement($attachment, $name, %opt); } #----------------------------------------------------------------------------- # insert a new item list sub insertItemList { my $self = shift; my $path = shift; my $posnode = (ref $path) ? $path : $self->getElement($path, shift); my %opt = @_; my $name = 'text:unordered-list'; $opt{'attribute'}{'text:style-name'} = $opt{'style'} if $opt{'style'}; $opt{'attribute'}{'text:style-name'} = $self->{'paragraph_style'} unless $opt{'attribute'}{'text:style-name'}; $opt{'attribute'}{'text:continue-numbering'} = $opt{'continue-numbering'} if $opt{'continue-numbering'}; if ($self->{'opendocument'}) { $name = 'text:list'; } else { if (defined $opt{'type'} && ($opt{'type'} eq 'ordered')) { $name = 'text:ordered-list' ; } } return $self->insertElement($posnode, $name, %opt); } #----------------------------------------------------------------------------- # row expansion utility for _expand_table sub _expand_row { my $self = shift; my $row = shift; unless ($row) { warn "[" . __PACKAGE__ . "::_expand_row] " . "Unknown table row\n"; return undef; } my $width = shift; my @cells = $row->selectChildElements ('table:(covered-|)table-cell'); my $cell = undef; my $last_cell = undef; my $rep = 0; my $cellnum = 0; while (@cells) { last if (defined $width and ($cellnum >= $width)); $cell = shift @cells; $last_cell = $cell; $rep = $cell ? $cell->getAttribute($COL_REPEAT_ATTRIBUTE) : 0; if ($rep) { $cell->removeAttribute($COL_REPEAT_ATTRIBUTE); while ($rep > 1) { last if (defined $width and ($cellnum >= $width)); $last_cell = $last_cell->replicateNode; $rep--; $cellnum++; } } $cellnum++ if $cell; } $last_cell->setAttribute($COL_REPEAT_ATTRIBUTE, $rep) if ($rep && ($rep > 1)); return $cellnum; } #----------------------------------------------------------------------------- # column expansion utility for _expand_table sub _expand_columns { my $self = shift; my $table = shift; return undef unless ($table && ref $table); my $width = shift; my @cols = $table->children('table:table-column'); my $col = undef; my $last_col = undef; my $rep = 0; my $colnum = 0; while (@cols) { last if (defined $width and ($colnum >= $width)); $col = shift @cols; $last_col = $col; $rep = $col ? $col->getAttribute($COL_REPEAT_ATTRIBUTE) : 0; if ($rep) { $col->removeAttribute($COL_REPEAT_ATTRIBUTE); while ($rep > 1) { last if (defined $width and ($colnum >= $width)); $last_col = $last_col->replicateNode; $rep--; $colnum++; } } $colnum++ if $col; } $last_col->setAttribute($COL_REPEAT_ATTRIBUTE, $rep) if ($rep && ($rep > 1)); return $colnum; } #----------------------------------------------------------------------------- # expands repeated table elements in order to address them in spreadsheets # in the same way as in text documents sub _expand_table { my $self = shift; my $table = shift; my $length = shift || $self->{'max_rows'}; my $width = shift || $self->{'max_cols'}; return undef unless ($table && ref $table); if ($length && ($length eq 'full')) { $length = undef; $width = undef; } my $new_width = $self->_expand_columns($table, $width); my @rows = (); my $header = $table->first_child('table:table-header-rows'); @rows = $header->children('table:table-row') if $header; push @rows, $table->children('table:table-row'); my $row = undef; my $last_row = undef; my $rep = 0; my $rownum = 0; while (@rows) { last if (defined $length and ($rownum >= $length)); $row = shift @rows; $last_row = $row; my $last_width = $self->_expand_row($row, $width); $new_width = $last_width if $last_width > $new_width; $rep = $row ? $row->getAttribute($ROW_REPEAT_ATTRIBUTE) : 0; if ($rep) { $row->removeAttribute($ROW_REPEAT_ATTRIBUTE); while ($rep > 1) { last if (defined $length and ($rownum >= $length)); $last_row = $last_row->replicateNode; $rep--; $rownum++; } } $rownum++ if $row; } $last_row->setAttribute($ROW_REPEAT_ATTRIBUTE, $rep) if ($rep && ($rep > 1)); return wantarray ? ($rownum, $new_width) : $table; } #----------------------------------------------------------------------------- # get a table size in ($lines, $columns) form sub getTableSize { my $self = shift; my $table = $self->getTable(@_) or return undef; my $height = 0; my $width = 0; my @rows = (); my $header = $table->first_child('table:table-header-rows'); @rows = $header->children('table:table-row') if $header; push @rows, $table->children('table:table-row'); foreach my $row (@rows) { my $rep = $row->getAttribute($ROW_REPEAT_ATTRIBUTE) || 1; $height += $rep; my @cells = $row->selectChildElements ('table:(covered-|)table-cell'); my $row_width = 0; foreach my $cell (@cells) { my $rep = $cell->getAttribute($COL_REPEAT_ATTRIBUTE); $row_width += $rep ? $rep : 1; } $width = $row_width if $row_width > $width; } return ($height, $width); } #----------------------------------------------------------------------------- # increases the size of an existing table # improved by Barry Slaymaker [rt.cpan.org #41975] sub expandTable { my $self = shift; my $table = shift; my $length = shift || 0; my $width = shift || 0; my $context = shift; my ($old_length, $old_width) = $self->getTableSize($table); $table = $self->normalizeSheet($table, 'full'); unless ($table) { warn "[" . __PACKAGE__ . "::expandTable] " . "Unknown or badly formed table\n"; return undef; } my $last_col = $self->getTableColumn($table, -1); my $last_row = $self->getRow($table, -1); my $i = 0; my $j = 0; # expand column declarations for ($i = $old_width ; $i < $width ; $i++) { $last_col = $last_col->replicateNode; } # expand existing rows for ($i = 0 ; $i < $old_length ; $i++) { my $row = $self->getTableRow($table, $i); my $last_cell = $self->getTableCell($row, -1); for ($j = $old_width ; $j < $width ; $j++) { $last_cell = $last_cell->replicateNode; } } # append new rows for ($i = $old_length; $i < $length; $i++) { $last_row = $last_row->replicateNode; } return wantarray ? $self->getTableSize($table) : $table; } #----------------------------------------------------------------------------- # get a table column descriptor element sub getTableColumn { my $self = shift; my $p1 = shift; return $p1 if (ref $p1 && $p1->isTableColumn); my $col = shift || 0; my $table = $self->getTable($p1, @_) or return undef; return $table->child($col, 'table:table-column'); } #----------------------------------------------------------------------------- # get/set a column style sub columnStyle { my $self = shift; my $p1 = shift; my $column = undef; if (ref $p1 && $p1->isTableColumn) { $column = $p1; } else { $column = $self->getTableColumn($p1, shift) or return undef; } my $newstyle = shift; return defined $newstyle ? $self->setAttribute($column, 'table:style-name' => $newstyle) : $self->getAttribute($column, 'table:style-name'); } #----------------------------------------------------------------------------- # get/set a row style sub rowStyle { my $self = shift; my $p1 = shift; my $row = undef; if (ref $p1 && $p1->isTableRow) { $row = $p1; } else { $row = $self->getTableRow($p1, shift) or return undef; } my $newstyle = shift; return defined $newstyle ? $self->setAttribute($row, 'table:style-name' => $newstyle) : $self->getAttribute($row, 'table:style-name'); } #----------------------------------------------------------------------------- # get a row element from table id and row num, # or the row cells if wantarray sub getTableRow { my $self = shift; my $p1 = shift; return $p1 if (ref $p1 && $p1->isTableRow); my $line = shift || 0; my $table = $self->getTable($p1, @_) or return undef; return $table->child($line, 'table:table-row'); } #----------------------------------------------------------------------------- # get a table header container sub getTableHeader { my $self = shift; my $table = $self->getTable(@_) or return undef; return $table->first_child('table:table-header-rows'); } #----------------------------------------------------------------------------- # get a header row in a table sub getTableHeaderRow { my $self = shift; my $p1 = shift; if (ref $p1) { if ($p1->isTableRow) { if ($p1->parent->hasTag('table:table-header-rows')) { return $p1; } else { return undef; } } } my $line = shift || 0; my $table = $self->getTable($p1, @_) or return undef; my $header = $table->first_child('table:table-header-rows') or return undef; return $header->child($line, 'table:table-row'); } #----------------------------------------------------------------------------- # insert a table header container sub copyRowToHeader { my $self = shift; my $row = $self->getTableRow(@_) or return undef; my $table = $row->parent; my $header = $table->first_child('table:table-header-rows'); unless ($header) { my $first_row = $self->getTableRow($table, 0); unless ($first_row) { warn "[" . __PACKAGE__ . "::createTableHeader] " . "Not allowed with an empty table\n"; return undef; } $header = $self->createElement('table:table-header-rows'); $header->paste_before($first_row); } my $header_row = $row->copy; $header_row->paste_last_child($header); return $header_row; } #----------------------------------------------------------------------------- # get all the rows in a table sub getTableRows { my $self = shift; my $table = $self->getTable(@_) or return undef; return $table->children('table:table-row'); } #----------------------------------------------------------------------------- # spreadsheet coordinates conversion utility sub _coord_conversion { my $arg = shift; return ($arg, @_) unless $arg; my $coord = uc $arg; return ($arg, @_) unless $coord =~ /[A-Z]/; $coord =~ s/\s*//g; $coord =~ /(^[A-Z]*)(\d*)/; my $c = $1; my $r = $2; return ($arg, @_) unless ($c && $r); my $rownum = $r - 1; my @csplit = split '', $c; my $colnum = 0; foreach my $p (@csplit) { $colnum *= 26; $colnum += ((ord($p) - ord('A')) + 1); } $colnum--; return ($rownum, $colnum, @_); } #----------------------------------------------------------------------------- # get cell element by 3D coordinates ($tablenum, $line, $column) # or by ($tablename/$tableref, $line, $column) sub getTableCell { my $self = shift; my $p1 = shift; return undef unless defined $p1; my $table = undef; my $row = undef; my $cell = undef; if (! ref $p1 || ($p1->isTable)) { @_ = OpenOffice::OODoc::Text::_coord_conversion(@_); my $r = shift || 0; my $c = shift || 0; if (ref $p1) { $table = $p1; } else { my $context = shift; unless (ref $context) { unshift @_, $context; $context = undef; } $table = $self->getTable($p1, $context) or return undef; } $row = $table->child($r, 'table:table-row') or return undef; $cell = ( $row->selectChildElements ('table:(covered-|)table-cell') )[$c]; } elsif ($p1->isTableCell) { $cell = $p1; } else # assume $p1 is a table row { $cell = $p1->selectChildElement ( 'table:(covered-|)table-cell', shift ); } return undef unless ($cell && ! $cell->isCovered); return wantarray ? ($cell, @_) : $cell; } #----------------------------------------------------------------------------- # adapted from a suggestion by dhoworth sub getCellPosition { my $self = shift; my $cell = $self->getTableCell(@_); unless ($cell && $cell->isTableCell) { warn "[" . __PACKAGE__ . "::cellPosition] " . "Non-cell argument\n"; return undef; } my $cp = $cell->pos() - 1; my $row = $cell->parent; my $rp = $row->pos('table:table-row') - 1; my $table = $row->parent; my $tp = $table->pos('table:table') - 1; return wantarray ? ($tp, $rp, $cp) : $tp; } #----------------------------------------------------------------------------- # get all the cells in a row sub getRowCells { my $self = shift; my $row = $self->getTableRow(@_) or return undef; return $row->children('table:table-cell'); } #----------------------------------------------------------------------------- sub getCellParagraph { my $self = shift; my $cell = $self->getTableCell(@_) or return undef; return $cell->first_child('text:p'); } #----------------------------------------------------------------------------- sub getCellParagraphs { my $self = shift; my $cell = $self->getTableCell(@_) or return undef; return $cell->children('text:p'); } #----------------------------------------------------------------------------- # get table cell value sub getCellValue { my $self = shift; my $cell = $self->getTableCell(@_) or return undef; my $prefix = $self->{'opendocument'} ? 'office' : 'table'; my $cell_type = $self->cellType($cell); if ((! $cell_type) || ($cell_type eq 'string')) # text value { return $self->getText($cell); } else { my $attribute = $self->cellValueAttributeName($cell); return $cell->att($attribute); } } #----------------------------------------------------------------------------- # get/set a cell value type sub cellValueType { my $self = shift; @_ = $self->getTableCell(@_); my $cell = shift or return undef; return $self->cellType($cell, @_); } #----------------------------------------------------------------------------- # get/set a cell currency sub fieldCurrency { my $self = shift; @_ = $self->getTableCell(@_); my $cell = shift or return undef; my $newcurrency = shift; my $prefix = $self->{'opendocument'} ? 'office' : 'table'; unless ($newcurrency) { return $cell->att($prefix . ':currency'); } else { $cell->set_att($prefix . ':value-type', 'currency'); return $cell->set_att($prefix . ':currency', $newcurrency); } } #----------------------------------------------------------------------------- # get/set accessor for the formula of a table cell sub cellFormula { my $self = shift; @_ = $self->getTableCell(@_); my $cell = shift or return undef; my $formula = shift; if (defined $formula) { if ($formula gt ' ') { $self->setAttribute($cell, 'table:formula', $formula); } else { $self->removeAttribute($cell, 'table:formula'); } } return $self->getAttribute($cell, 'table:formula'); } #----------------------------------------------------------------------------- # set value of an existing cell sub updateCell { my $self = shift; @_ = $self->getTableCell(@_); my $cell = shift or return undef; my $value = shift; my $text = shift; $text = $value unless defined $text; my $cell_type = $self->cellType($cell); unless ($cell_type) { $cell_type = 'string'; $self->cellType($cell, $cell_type); } my $p = $cell->first_child('text:p'); unless ($p) { $p = $self->createParagraph($text); $p->paste_last_child($cell); } else { $self->SUPER::setText($p, $text); } unless ($cell_type eq 'string') { my $attribute = $self->cellValueAttributeName($cell); $cell->setAttribute($attribute, $value); } return $cell; } #----------------------------------------------------------------------------- # get/set a cell value sub cellValue { my $self = shift; @_ = $self->getTableCell(@_); my $cell = shift or return undef; my $newvalue = shift; if (defined $newvalue) { $self->updateCell($cell, $newvalue, @_); } return $self->getCellValue($cell); } #----------------------------------------------------------------------------- # get/set a cell style sub cellStyle { my $self = shift; @_ = $self->getTableCell(@_); my $cell = shift or return undef; my $newstyle = shift; return defined $newstyle ? $self->setAttribute($cell, 'table:style-name' => $newstyle) : $self->getAttribute($cell, 'table:style-name'); } #----------------------------------------------------------------------------- # get/set cell spanning (from a contribution by Don_Reid[at]Agilent.com) sub removeCellSpan { my $self = shift; my $cell = $self->getTableCell(@_) or return undef; my $hspan = $cell->getAttribute('table:number-columns-spanned') || 1; $cell->removeAttribute('table:number-columns-spanned'); my $vspan = $cell->getAttribute('table:number-rows-spanned') || 1; $cell->removeAttribute('table:number-rows-spanned'); my $row = $cell->parent('table:table-row'); my $table = $row->parent('table:table'); my $vpos = $row->getLocalPosition; my $hpos = $cell->getLocalPosition(qr'table:(covered-|)table-cell'); my $vend = $vpos + $vspan - 1; my $hend = $hpos + $hspan - 1; my $cell_paragraph = $cell->first_child('text:p'); ROW: for (my $i = $vpos ; $i <= $vend ; $i++) { my $cr = $self->getRow($table, $i) or last ROW; CELL: for (my $j = $hpos ; $j <= $hend ; $j++) { my $covered = $cr->selectChildElement (qr 'table:(covered-|)table-cell', $j) or last CELL; next CELL if $covered == $cell; $covered->set_name('table:table-cell'); $covered->set_atts($cell->atts); $covered->removeAttribute('table:value'); if ($cell_paragraph) { my $p = $cell_paragraph->copy; $p->set_text(""); $p->paste_first_child($covered); } } } } sub cellSpan { my $self = shift; @_ = $self->getTableCell(@_); my $cell = shift or return undef; my $rnum = undef; my $cnum = undef; my $table = undef; my $old_hspan = $cell->att('table:number-columns-spanned') || 1; my $old_vspan = $cell->att('table:number-rows-spanned') || 1; my $hspan = shift; my $vspan = shift; unless ($hspan || $vspan) { return wantarray ? ($old_hspan, $old_vspan) : $old_hspan; } $hspan = $old_hspan unless $hspan; $vspan = $old_vspan unless $vspan; $self->removeCellSpan($cell); my $row = $cell->parent('table:table-row'); $table = $row->parent('table:table') unless $table; my $vpos = $row->getLocalPosition; my $hpos = $cell->getLocalPosition(qr'table:(covered-|)table-cell'); my $hend = $hpos + $hspan - 1; my $vend = $vpos + $vspan - 1; $cell->setAttribute('table:number-columns-spanned', $hspan); $cell->setAttribute('table:number-rows-spanned', $vspan); ROW: for (my $i = $vpos ; $i <= $vend ; $i++) { my $cr = $self->getRow($table, $i) or last ROW; CELL: for (my $j = $hpos ; $j <= $hend ; $j++) { my $covered = $self->getCell($cr, $j) or last CELL; next CELL if $covered == $cell; my @paras = $covered->children('text:p'); while (@paras) { my $p = shift @paras; $p->paste_last_child($cell) if (defined $p->text && $p->text ge ' '); } $self->removeCellSpan($covered); $covered->set_name('table:covered-table-cell'); } } return wantarray ? ($hspan, $vspan) : $hspan; } #----------------------------------------------------------------------------- # get the content of a table element in a 2D array sub _get_row_content { my $self = shift; my $row = shift; my @row_content = (); foreach my $cell ($row->children('table:table-cell')) { push @row_content, $self->getText($cell); } return @row_content; } sub getTableText { my $self = shift; my $table = $self->getTable(shift); return undef unless $table; my @table_content = (); my $headers = $table->getFirstChild('table:table-header-rows'); if ($headers) { push @table_content, [ $self->_get_row_content($_) ] for ($headers->children('table:table-row')); } push @table_content, [ $self->_get_row_content($_) ] for ($table->children('table:table-row')); if (wantarray) { return @table_content; } else { my $delimiter = $self->{'field_separator'} || ''; my $line_break = $self->{'line_separator'} || ''; my @list = (); foreach my $row (@table_content) { push @list, join($delimiter, @{$row}); } return join $line_break, @list; } } #----------------------------------------------------------------------------- # get table element selected by number sub getTable { my $self = shift; my $table = shift; my $length = shift; my $width = shift; my $context = shift; if (ref $length) { $context = $length; $length = undef; $width = undef; } elsif (ref $width) { $context = $width; $width = undef; $length = undef; } return undef unless defined $table; my $t = undef; if (ref $table) { if ($table->isTable) { $t = $table; } else { warn "[" . __PACKAGE__ . "::getTable] " . "Non table object\n"; return undef; } } else # retrieve table by number or name { if (($table =~ /^\d*$/) || ($table =~ /^[\d+-]\d+$/)) { $t = $self->getElement ('//table:table', $table, $context); } unless ($t) { my $n = $self->inputTextConversion($table); $t = $self->getNodeByXPath ( "//table:table[\@table:name=\"$n\"]" ); } } return undef unless $t; if ( $length || ( $self->{'expand_tables'} && ($self->{'expand_tables'} eq 'on') ) ) { $length = 'full' if ($length && ($length eq 'normalize')); return $self->_expand_table($t, $length, $width); } return wantarray ? $self->getTableSize($t) : $t; } #----------------------------------------------------------------------------- sub getTableByName { my $self = shift; my $name = $self->inputTextConversion(shift); my $table = $self->getNodeByXPath ("//table:table[\@table:name=\"$name\"]"); return $self->getTable($table, @_); } #----------------------------------------------------------------------------- # user-controlled spreadsheet expansion sub normalizeSheet { my $self = shift; my $table = shift; my $length = shift; my $width = shift; my $context = shift; unless (ref $table) { if ($table =~ /^\d*$/) { $table = $self->getElement ('//table:table', $table, $context); } else { my $n = $self->inputTextConversion($table); $table = $self->getNodeByXPath ( "//table:table[\@table:name=\"$n\"]", $context ); } } unless ((ref $table) && $table->isTable) { warn "[" . __PACKAGE__ . "::normalizeSheet] " . "Missing sheet\n"; return undef; } return $self->_expand_table($table, $length, $width, @_); } sub normalizeSheets { my $self = shift; my $length = shift; my $width = shift; my @sheets = $self->getTableList; my $count = 0; foreach my $sheet (@sheets) { $self->normalizeSheet($sheet, $length, $width, @_); $count++; } return $count; } #----------------------------------------------------------------------------- # activate/deactivate and parametrize automatic spreadsheet expansion sub autoSheetNormalizationOn { my $self = shift; my $length = shift || $self->{'max_rows'}; my $width = shift || $self->{'max_cols'}; $self->{'expand_tables'} = 'on'; $self->{'max_rows'} = $length; $self->{'max_cols'} = $width; return 'on'; } sub autoSheetNormalizationOff { my $self = shift; my $length = shift || $self->{'max_rows'}; my $width = shift || $self->{'max_cols'}; $self->{'expand_tables'} = 'no'; $self->{'max_rows'} = $length; $self->{'max_cols'} = $width; return 'no'; } #----------------------------------------------------------------------------- # common code for insertTable and appendTable sub _build_table { my $self = shift; my $table = shift; my $rows = shift || $self->{'max_rows'} || 1; my $cols = shift || $self->{'max_cols'} || 1; my %opt = ( 'cell-type' => 'string', 'text-style' => 'Table Contents', @_ ); $rows = $self->{'max_rows'} unless $rows; $cols = $self->{'max_cols'} unless $cols; my $col_proto = $self->createElement('table:table-column'); $self->setAttribute ($col_proto, 'table:style-name', $opt{'column-style'}) if $opt{'column-style'}; $col_proto->paste_first_child($table); $col_proto->replicateNode($cols - 1, 'after'); my $row_proto = $self->createElement('table:table-row'); my $cell_proto = $self->createElement('table:table-cell'); $self->cellValueType($cell_proto, $opt{'cell-type'}); $self->cellStyle($cell_proto, $opt{'cell-style'}); if ($opt{'paragraphs'}) { my $para_proto = $self->createElement('text:p'); $self->setAttribute ($para_proto, 'text:style-name', $opt{'text-style'}) if $opt{'text-style'}; $para_proto->paste_last_child($cell_proto); } $cell_proto->paste_first_child($row_proto); $cell_proto->replicateNode($cols - 1, 'after'); $row_proto->paste_last_child($table); $row_proto->replicateNode($rows - 1, 'after'); return $table; } #----------------------------------------------------------------------------- # create a new table and append it to the end of the document body (default), # or attach it as a new child of a given element sub appendTable { my $self = shift; my $name = shift; my $rows = shift || $self->{'max_rows'} || 1; my $cols = shift || $self->{'max_cols'} || 1; my %opt = ( 'attachment' => $self->{'body'}, 'table-style' => $name, @_ ); if ($self->getTable($name, $self->{'xpath'})) { warn "[" . __PACKAGE__ . "::appendTable] " . "Table $name exists\n"; return undef; } my $table = $self->appendElement ( $opt{'attachment'}, 'table:table', attribute => { 'table:name' => $name, 'table:style-name' => $opt{'table-style'} } ) or return undef; return $self->_build_table($table, $rows, $cols, %opt); } #----------------------------------------------------------------------------- sub insertTable { my $self = shift; my $path = shift; my $pos = ref $path ? undef : shift; my $name = shift; my $rows = shift || $self->{'max_rows'} || 1; my $cols = shift || $self->{'max_cols'} || 1; my %opt = ( 'table-style' => $name, @_ ); my $posnode = $self->getElement($path, $pos, $opt{'context'}) or return undef; if ($self->getTable($name, $self->{'xpath'})) { warn "[" . __PACKAGE__ . "::insertTable] " . "Table $name exists\n"; return undef; } my $table = $self->insertElement ( $posnode, 'table:table', attribute => { 'table:name' => $name, 'table:style-name' => $opt{'table-style'} }, %opt ) or return undef; return $self->_build_table($table, $rows, $cols, %opt); } #----------------------------------------------------------------------------- sub renameTable { my $self = shift; my $table = $self->getTable(shift) or return undef; my $newname = shift; if ($self->getTable($newname, $self->{'xpath'})) { warn "[" . __PACKAGE__ . "::renameTable] " . "Table name $newname already in use\n"; return undef; } return $self->setAttribute($table, 'table:name' => $newname); } #----------------------------------------------------------------------------- sub tableName { my $self = shift; my $table = $self->getTable(shift) or return undef; my $newname = shift; if (ref $newname) { unshift @_, $newname; $newname = undef; } $self->renameTable($table, $newname, @_) if $newname; return $self->getAttribute($table, 'table:name', @_); } #----------------------------------------------------------------------------- sub tableStyle { my $self = shift; my $table = $self->getTable(shift) or return undef; my $newstyle = shift; if (ref $newstyle) { unshift @_, $newstyle; $newstyle = undef; } return defined $newstyle ? $self->setAttribute ($table, 'table:style-name' => $newstyle, @_) : $self->getAttribute ($table, 'table:style-name', @_); } #----------------------------------------------------------------------------- # replicates a column in a normalized table sub insertTableColumn { my $self = shift; my $table = shift; my $col_num = shift; my %options = ( position => 'before', @_ ); $table = $self->getTable($table, $options{'context'}) or return undef; my ($height, $width) = $self->getTableSize($table); unless ($col_num < $width) { warn "[" . __PACKAGE__ . "::replicateTableColumn] " . "Column number out of range\n"; return undef; } $self->_expand_columns($table, $width); my $column = $table->child($col_num, 'table:table-column'); my $new_cell = undef; if ($column) { my $new_column = $column->copy; $new_column->paste($options{position}, $column); } my @rows = (); my $header = $table->first_child('table:table-header-rows'); @rows = $header->children('table:table-row') if $header; push @rows, $self->getTableRows($table); foreach my $row (@rows) { my $cell = $row->selectChildElement ('table:(covered-|)table-cell', $col_num) or next; $new_cell = $cell->copy; $new_cell->paste($options{'position'}, $cell); } return $column || $new_cell; } #----------------------------------------------------------------------------- # delete a column in a table sub deleteTableColumn { my $self = shift; my $p1 = shift; my $col_num = shift; my $table = undef; if (ref $p1 && $p1->isTableColumn) { $table = $p1->parent; $col_num = $p1->getLocalPosition; } else { $table = $p1; } $table = $self->getTable($table); unless ($table) { warn "[" . __PACKAGE__ . "::deleteTableColumn] " . "Unknown table\n"; return undef; } my ($height, $width) = $self->getTableSize($table); unless (defined $col_num) { warn "[" . __PACKAGE__ . "::deleteTableColumn] " . "Missing column position\n"; return undef; } $self->_expand_columns($table, $width); my $column = $table->child($col_num, 'table:table-column'); $column->delete if $column; my @rows = (); my $header = $table->first_child('table:table-header-rows'); @rows = $header->children('table:table-row') if $header; push @rows, $self->getTableRows($table); foreach my $row (@rows) { my $cell = $row->selectChildElement ('table:(covered-|)table-cell', $col_num) or next; $cell->delete; } return 1; } #----------------------------------------------------------------------------- # replicates a row in a table sub replicateTableRow { my $self = shift; my $p1 = shift; my $table = undef; my $row = undef; my $line = undef; if (ref $p1 && $p1->isTableRow) { $row = $p1; } else { $line = shift; } my %options = ( position => 'after', @_ ); if (defined $line) { $row = $self->getTableRow($p1, $line, $options{'context'}) or return undef; } return $self->replicateElement($row, $row, %options); } #----------------------------------------------------------------------------- # replicate a row and insert the clone before (default) or after the prototype sub insertTableRow { my $self = shift; my $p1 = shift; my $row = undef; my $line = undef; if (ref $p1) { if ($p1->isTableRow) { $row = $p1; } else { $line = shift; $row = $self->getTableRow($p1, $line); } } else { $row = $self->getTableRow($p1, shift); } return undef unless $row; my %options = ( position => 'before', @_ ); return $self->replicateTableRow($row, %options); } #----------------------------------------------------------------------------- # append a new row (replicating the last existing one) to a table sub appendTableRow { my $self = shift; my $table = shift; return $self->replicateTableRow($table, -1, position => 'after', @_); } #----------------------------------------------------------------------------- # delete a given table row sub deleteTableRow { my $self = shift; my $row = $self->getTableRow(@_) or return undef; return $self->removeElement($row); } #----------------------------------------------------------------------------- # update the user field references according to the internal value sub updateUserFieldReferences { my $self = shift; my $fd = shift or return undef; my $context = shift; my $field_decl = undef; my $name = undef; if (ref $fd) { $name = $self->getAttribute($fd, 'text:name'); $field_decl = $fd; } else { $field_decl= $self->getUserField($fd, $context); $name = $fd; } unless ($field_decl && $name) { warn "[" . __PACKAGE__ . "::updateUserFieldReferences] " . "Unknown or bad user field\n"; return undef; } my @fields = $self->selectNodesByXPath ("//text:user-field-get[\@text:name=\"$name\"]", $context); my $content = $self->userFieldValue($field_decl) || ""; my $count = 0; foreach my $field (@fields) { $self->setText($field, $content); $count++; } return $count; } #----------------------------------------------------------------------------- # get user field references sub getUserFieldReferences { my $self = shift; my $name = $self->inputTextConversion(shift); my $xp = undef; my @list = (); $xp = (defined $name && $name gt "") ? "//text:user-field-get[\@text:name=\"$name\"]" : "//text:user-field-get"; @list = $self->selectNodesByXPath($xp, @_); $xp = (defined $name && $name gt "") ? "//text:user-field-input[\@text:name=\"$name\"]" : "//text:user-field-input"; push @list, $self->selectNodesByXPath($xp, @_); return @list; } #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # create a new paragraph sub createParagraph { my $self = shift; my $text = shift; my $style = shift || "Standard"; my $p = OpenOffice::OODoc::XPath::new_element('text:p'); if (defined $text) { $self->SUPER::setText($p, $text); } $self->setAttribute($p, 'text:style-name' => $style); return $p; } #----------------------------------------------------------------------------- # inserts a flat text string within a given text element sub insertString { my $self = shift; my $path = shift; my $pos = ref $path ? undef : shift; my $element = $self->getElement($path, $pos) or return undef; my $text = shift; my $offset = shift; return $element->insertTextChild($text, $offset); } #----------------------------------------------------------------------------- # add a new or existing text at the end of the document sub appendText { my $self = shift; my $name = shift; my %opt = @_; my $attachment = $opt{'attachment'} || $self->{'body'}; $opt{'attribute'} = $opt{'attributes'} unless ($opt{'attribute'}); $opt{'attribute'}{'text:style-name'} = $opt{'style'} if $opt{'style'}; unless ((ref $name) || $opt{'attribute'}{'text:style-name'}) { $opt{'attribute'}{'text:style-name'} = $self->{'paragraph_style'}; } delete $opt{'attachment'}; delete $opt{'style'}; return $self->appendElement($attachment, $name, %opt); } #----------------------------------------------------------------------------- # insert a new or existing text element before or after an given element sub insertText { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $name = shift; my %opt = @_ ; $opt{'attribute'}{'text:style-name'} = $opt{'style'} if $opt{'style'}; return (ref $path) ? $self->insertElement($path, $name, %opt) : $self->insertElement($path, $pos, $name, %opt); } #----------------------------------------------------------------------------- # create and add a new paragraph at the end of the document sub appendParagraph { my $self = shift; my %opt = ( style => $self->{'paragraph_style'}, @_ ); my $paragraph = $self->createParagraph($opt{'text'}, $opt{'style'}); my $attachment = $opt{'attachment'} || $self->{'body'}; $paragraph->paste_last_child($attachment); return $paragraph; } #----------------------------------------------------------------------------- # add a new heading at the end of the document sub appendHeading { my $self = shift; my %opt = ( style => $self->{'heading_style'}, level => '1', @_ ); $opt{'attribute'}{$self->{'level_attr'}} = $opt{'level'}; return $self->appendText('text:h', %opt); } #----------------------------------------------------------------------------- # insert a new paragraph at a given position sub insertParagraph { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my %opt = ( style => $self->{'paragraph_style'}, @_ ); return (ref $path) ? $self->insertText($path, 'text:p', %opt) : $self->insertText($path, $pos, 'text:p', %opt); } #----------------------------------------------------------------------------- # insert a new heading at a given position sub insertHeading { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my %opt = ( style => $self->{'heading_style'}, level => '1', @_ ); $opt{'attribute'}{$self->{'level_attr'}} = $opt{'level'}; return (ref $path) ? $self->insertText($path, 'text:h', %opt) : $self->insertText($path, $pos, 'text:h', %opt); } #----------------------------------------------------------------------------- # remove the paragraph element at a given position sub removeParagraph { my $self = shift; my $pos = shift; return $self->removeElement($pos) if (ref $pos); return $self->removeElement('//text:p', $pos); } #----------------------------------------------------------------------------- # remove the heading element at a given position sub removeHeading { my $self = shift; my $element = $self->getHeading(@_); return $self->removeElement($element); } #----------------------------------------------------------------------------- sub textStyle { my $self = shift; my $path = shift; my $pos = (ref $path) ? undef : shift; my $element = $self->getElement($path, $pos) or return undef; my $newstyle = shift; if (ref $newstyle) { $newstyle = $self->getAttribute($newstyle, 'style:name'); unless ($newstyle) { warn "[" . __PACKAGE__ . "::textStyle] " . "Bad text style\n"; return undef; } } my $expression = shift; if (defined $expression) { return $self->setSpan($element, $expression, $newstyle); } if ($element->isListItem) { return defined $newstyle ? $self->setItemStyle($element) : $self->getItemStyle($element); } else { return defined $newstyle ? $self->setAttribute ($element, 'text:style-name' => $newstyle) : $self->getAttribute($element, 'text:style-name'); } } #----------------------------------------------------------------------------- package OpenOffice::OODoc::Element; #----------------------------------------------------------------------------- # text element type detection (add-in for OpenOffice::OODoc::Element) BEGIN { *headerLevel = *headingLevel; *isHeader = *isHeading; } sub isOrderedList { my $element = shift; return $element->hasTag('text:ordered-list'); } sub isUnorderedList { my $element = shift; return $element->hasTag('text:unordered-list'); } sub isItemList { my $element = shift; my $name = $element->getName; return ($name =~ /^text:.*list$/) ? 1 : undef; } sub isListItem { my $element = shift; return $element->hasTag('text:list-item'); } sub isParagraph { my $element = shift; return $element->hasTag('text:p'); } sub isHeading { my $element = shift; return $element->hasTag('text:h'); } sub headingLevel { my $element = shift; my $level = $element->getAttribute('text:outline-level'); return defined $level ? $level : $element->getAttribute('text:level'); } sub isTable { my $element = shift; return $element->hasTag('table:table'); } sub isTableRow { my $element = shift; return $element->hasTag('table:table-row'); } sub isTableColumn { my $element = shift; return $element->hasTag('table:table-column'); } sub isTableCell { my $element = shift; return $element->hasTag('table:table-cell'); } sub isCovered { my $element = shift; my $name = $element->getName; return ($name && ($name =~ /covered/)) ? 1 : undef; } sub isSpan { my $element = shift; return $element->hasTag('text:span'); } sub isHyperlink { my $element = shift; return $element->hasTag('text:a'); } sub checkNoteClass { my ($element, $class) = @_; my $name = $element->getName; return 1 if $name eq "text:$class"; return undef unless $name eq 'text:note'; my $elt_class = $element->att('text:note-class'); return ($elt_class && ($elt_class eq $class)); } sub getNoteClass { my $element = shift; return undef unless $element->isNote; my $class = $element->att('text:note-class'); return $class if $class; my $tagname = $element->getName; $tagname =~ /^text:(endnote|footnote)$/; return $1; } sub isEndnote { my $element = shift; return $element->checkNoteClass('endnote'); } sub isFootnote { my $element = shift; return $element->checkNoteClass('footnote'); } sub checkNoteBodyClass { my ($element, $class) = @_; my $name = $element->getName; return ($name eq "text:$class-body") ? 1 : $element->parent->checkNoteClass($class); } sub checkNoteCitationClass { my ($element, $class) = @_; my $name = $element->getName; return ($name eq "text:$class-citation") ? 1 : $element->parent->checkNoteClass($class); } sub isFootnoteCitation { my $element = shift; return $element->checkNoteCitationClass('footnote'); } sub isEndnoteCitation { my $element = shift; return $element->checkNoteCitationClass('endnote'); } sub isEndnoteBody { my $element = shift; return $element->checkNoteBodyClass('endnote'); } sub isFootnoteBody { my $element = shift; return $element->checkNoteBodyClass('footnote'); } sub isNoteBody { my $element = shift; my $tag = $element->name; return $tag =~ /^text:(|foot|end)note-body$/; } sub isNoteCitation { my $element = shift; my $tag = $element->name; return $tag =~ /^text:(|foot|end)note-citation$/; } sub isNote { my $element = shift; my $tag = $element->name; return $tag =~ /^text:(|foot|end)note$/; } sub isSequenceDeclarations { my $element = shift; return $element->hasTag('text:sequence-decls'); } sub isBibliographyMark { my $element = shift; return $element->hasTag('text:bibliography-mark'); } sub isDrawPage { my $element = shift; return $element->hasTag('draw:page'); } sub isSection { my $element = shift; return $element->hasTag('text:section'); } sub isTextBox { my $element = shift; my $name = $element->getName or return undef; if ($name eq 'draw:frame') { my $child = $element->first_child('draw:text-box'); return $child ? 1 : undef; } else { return ($name eq 'draw:text-box') ? 1 : undef; } } sub textId { my $element = shift; my $id = shift; my $id_attr = 'text:id'; if (defined $id) { $element->set_att($id_attr => $id); } return $element->att($id_attr); } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/OODoc/Manifest.pm0000644000175000017500000000630611415443261015672 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : Manifest.pm 2.007 2010-07-07 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2008 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- package OpenOffice::OODoc::Manifest; use 5.008_000; use strict; our $VERSION = '2.007'; use OpenOffice::OODoc::XPath 2.237; our @ISA = qw ( OpenOffice::OODoc::XPath ); #----------------------------------------------------------------------------- # constructor : calling odfXPath constructor with 'manifest' as member choice sub new { my $caller = shift; my $class = ref ($caller) || $caller; my %options = ( part => 'META-INF/manifest.xml', element => 'manifest:manifest', body_path => '/manifest:manifest', @_ ); my $object = $class->SUPER::new(%options); return $object ? bless $object, $class : undef; } #----------------------------------------------------------------------------- # override the basic getBody() method; here the body is the root sub getBody { my $self = shift; return $self->getRootElement; } #----------------------------------------------------------------------------- # retrieving an entry by content sub getEntry { my ($self, $entry) = @_; return $self->selectElementByAttribute ('manifest:file-entry', 'manifest:full-path' => $entry); } #----------------------------------------------------------------------------- # get the entry describing the OpenOffice.org mime type of the document sub getMainEntry { my $self = shift; return $self->getEntry('/'); } #----------------------------------------------------------------------------- # set the entry describing the OpenOffice.org mime type of the document sub setMainEntry { my $self = shift; return $self->setEntry("/", @_); } #----------------------------------------------------------------------------- # get the media type of a given entry sub getType { my ($self, $entry) = @_; my $element = $self->getEntry($entry); return $element ? $self->getAttribute($element, 'manifest:media-type') : undef; } #----------------------------------------------------------------------------- # get the media type of the main entry sub getMainType { my $self = shift; return $self->getType("/"); } #----------------------------------------------------------------------------- # remove an entry sub removeEntry { my ($self, $entry) = @_; my $element = $self->getEntry($entry); return $element ? $self->removeElement($element) : undef; } #----------------------------------------------------------------------------- # create a new entry sub setEntry { my $self = shift; my $entry = shift; my $type = shift || ''; my $element = $self->getEntry($entry); unless ($element) { $element = $self->appendElement ( $self->getBody(), 'manifest:file-entry' ); $self->setAttribute($element, 'manifest:media-type', $type); $self->setAttribute($element, 'manifest:full-path', $entry); } else { $self->setAttribute($element, 'manifest:media-type', $type); } return $element; } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/OODoc/Meta.pod0000644000175000017500000002646311327772124015173 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::Meta - Access to document metadata =head1 DESCRIPTION The OpenOffice::OODoc::Meta class is a specialist derivative of OpenOffice::OODoc::XPath for XML members which describe the metadata of ODF documents. =head2 Methods =head3 Constructor : OpenOffice::OODoc::Meta->new() Short Form: odfMeta() See OpenOffice::OODoc::XPath->new (or odfXPath) Returns an OpenDocument connector allowing subsequent access to the metadata of a well-formed, ODF-compliant document. The XML member loaded by default is 'meta.xml'. Example: $my meta = odfMeta(file => 'document.odt'); returns a new object which represents the metadata of the ODF document "document.odt". =head3 addKeyword(text) Adds the given text to the list of document keywords if not already found. Example: $meta->addKeyword("document management"); $meta->addKeyword("office"); $meta->addKeyword("tech watch"); =head3 creation_date() Without argument, returns the document's creation date in ISO-8601 format (i.e. the ODF-compliant date format). Example of returned value: 2008-11-12T08:22:50 The returned value can be converted in standard numeric time format with the ooTimelocal() function. With argument, inserts the given string (without checking) as the creation date. The argument, if any, must comply with the ODF (ISO-8601) date format. The ooLocaltime() function can be used in order to convert a regular Perl time() value in ODF. A conventional editing software should never changes this value, but this method allows the user to read or write it. See also date(). =head3 creator() Without argument, returns the document creator's name. The creator is generally the author of the last update. See also initial_creator(). With argument, modifies the document author's name. =head3 date() Without argument, returns the document's date of last modification, in ISO-8601 format. With argument, inserts the given string (without checking) as the last modification date. The argument, if any, must comply with the ODF date format (ISO-8601). The odfLocaltime() function can be used in order to convert a regular Perl time() value in ODF format. The returned value can be converted in standard numeric time format with the ooTimelocal() function. =head3 description() Without argument, returns the contents of the document properties "Description" field. With argument, inserts the given text in the "Description" field. =head3 editing_cycles() Without argument, returns the number of edit sessions (i.e. saves, under an ODF-compliant editing software). Or, technically, the number of versions. With argument, modifies this number without checking. See also increment_editing_cycles(). =head3 editing_duration() Without argument, returns the total editing time for the document, in ISO-8601 date/time format. For example, the returned string can be: P2DT11H27M33S which in this case means that the document has been edited for 2 days, 11 hours, 27 minutes and 33 seconds. With argument, forces a new value into this property without checking. =head3 generator() Without argument, returns a label representing the signature of the software which generated the document. Example of signature: "OpenOffice.org/3.1$Unix OpenOffice.org_project/310m11$Build-9399" With argument, inserts any signature. =head3 getTemplate() Returns information about the template that is linked to the current document, if any. In scalar context, the returned information is the location (URL) of the template, or undef if the document isn't linked to any template. In array context, the returned values are, in this order, the location of the template document, the date and time when the template was last modified prior to being used to create the current document, then the title of the template document. See also unlinkTemplate(). =head3 getUserProperty(name) Retrieves the content of the user-defined property corresponding to the given name, if any. The argument may be an already retrieved user-defined property element reference instead of a name. In scalar context, returns the value only. In array context, returns the data type (1st) and the value (2nd). Returns undef if the property is not defined. See also setUserProperty(). =head3 getUserPropertyElement(name) Like getUserProperty(), but the return value is the user property element instead of its value and/or type. Returns undef if the given property is not defined (or if the given object is not a user-defined property element). =head3 getUserPropertyElements() Returns the list of all the existing user-defined property elements. =head3 increment_editing_cycles() Adds 1 to the editing cycle count that is stored in the document, and returns the new count. This count should be incremented each time the document is edited through an ODF-compliant application. See also editing_cycles(). =head3 initial_creator() Like creator(), but apply to the creator of the first version of the document. The OOo desktop software never updates this value, but this method allows the user to read or write it. =head3 keywords() Without argument, returns a list of the document's keywords. In a list context, the result is a table where each element is a keyword. In a scalar context, the keywords are returned in a single character string, each of which is separated by a comma and a space. With arguments, adds a list of keywords to the existing one. The only checking carried out is to see if the keyword already exists, if so it is not added. =head3 language() Without argument, returns the content of the language variable. Example: fr_FR With argument, changes the content of this variable without checking. =head3 removeKeyword(keyword) Removes the given keyword if it exists. =head3 removeUserProperty(name) Deletes the user-defined property corresponding to the given name (does nothing if the given property is not defined in the document). =head3 removeUserProperties() Deletes all the existing user-defined properties and returns the number or really deleted elements (does nothing and returns 0 if no user property is defined in the document). =head3 setUserProperty(name, type => value_type, value => text) Creates or updates a user-defined property with the given data type and the given value. According to the ODF specification, the presently allowed data types in a meta property are float, date, time, boolean and string. However, the given type is not checked so the application can provide any abritrary type. While the consistency between the type and the value is not checked, the ODF compliance requires 'true' or 'false' for a boolean, and the ISO-8601 format for a date (see odfLocaltime()). Example: $meta->setUserProperty ( "Approved", type => 'boolean', value => 'false' ); $meta->setUserProperty ( "Circulation", type => 'string', value => "Internal" ); $meta->setUserProperty ( "Release date", type => 'date', value => '2010-01-01' ); $meta->setUserProperty ( "Release number", type => 'float', value => 5.4 ); The 'type' option allows to change the data type of an existing user- defined property. However, if a new property is created without 'type' option, the 'string' type is selected by default. The return value reflects the new status of the user defined item, in the same format as with getUserProperty(). =head3 statistic() Without argument, returns a hash which represents the entire "statistics" section of the metadata. The content depends on the type of document. Text 'meta:table-count' => number of tables 'meta:image-count' => number of images 'meta:object-count' => number of OLE objects 'meta:page-count' => number of pages 'meta:paragraph-count' => number of paragraphs 'meta:word-count' => number of words 'meta:character-count' => number of characters Spreadsheet 'meta:table-count' => number of sheets 'meta:cell-count' => number of non-empty cells 'meta:object-count' => number of objects (images, etc.) Example: my $meta = odfMeta->new("invoice.ods"); my %stat = $meta->statistic; print "This invoice contains " . "$stat{'meta:cell-count'} cells and " . "$stat{'meta:table-count'} pages\n"; With arguments, you can modify (or falsify ?!) all or some of the statistical data and even create attributes which are not created by the office software. Arguments are passed in pairs [key => value] and handled without checking. Example: $meta->statistic ('meta:table-count' => '4', 'status' => 'OK'); This example forces the number of tables to 4 (whatever the reality) and adds an arbitrary attribute 'status' with value 'OK'. Note : Such forced attributes do not upset the function of the office software which ignores them. They could therefore be useful in programs which handle documents out of reach of the end user. However, if such a document is then edited or updated by a typical end-user desktop application, these "foreign" attributes could be lost and and replaced by what this application considers to be the "real" values to those attributes it manages. =head3 subject() Without argument, returns the document's subject. With argument, adds a new subject to the document. =head3 title() Without argument, returns the document's title. With argument, adds a new title to the document. =head3 unlinkTemplate() Removes the reference to a template document, if any, ensuring that the document no longer depends on any external template. =head3 user_defined() Returns the list of the user defined fields of the document. The list is returned in the form of a hash elements whose keys represent the field names and whose values represent their content. By supplying a similar hash of elements as an argument, this method deletes and replaces the existing content. However, if the number of provided items is less than the numbers of existing user defined properties, the exceding properties are left unchanged. Warning: this method is deprecated. Newer methods such as setUserProperty(), getUserProperty(), removeUserProperties(), getUserPropertyElements(), and getUserPropertyElements() should be preferred. =head3 version() Synonym of editing_cycles(). =head2 Properties As for OpenOffice::OODoc::XPath =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2010 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/Document.pm0000644000175000017500000001312011415443453015675 0ustar jmgjmg#----------------------------------------------------------------------------- # # $Id : Document.pm 2.025 2010-07-08 JMG$ # # Created and maintained by Jean-Marie Gouarne # Copyright 2008 by Genicorp, S.A. (www.genicorp.com) # #----------------------------------------------------------------------------- use OpenOffice::OODoc::Text 2.243; use OpenOffice::OODoc::Image 2.020; use OpenOffice::OODoc::Styles 2.027; package OpenOffice::OODoc::Document; use 5.008_000; use strict; our @ISA = qw ( OpenOffice::OODoc::Text OpenOffice::OODoc::Image OpenOffice::OODoc::Styles ); our $VERSION = '2.025'; #----------------------------------------------------------------------------- # constructor sub new { my $caller = shift; my $class = ref ($caller) || $caller; my %options = ( part => 'content', @_ ); my $object = $class->SUPER::new(%options); return $object ? bless $object, $class : undef; } #----------------------------------------------------------------------------- # create and append a new image style sub createImageStyle { my $self = shift; my $stylename = shift; my %opt = ( %OpenOffice::OODoc::Image::DEFAULT_IMAGE_STYLE, @_ ); $opt{'family'} = $self->{'opendocument'} ? 'graphic' : 'graphics'; return $self->createStyle($stylename, %opt); } #----------------------------------------------------------------------------- # create and append a new text style # default attributes come from the 'Standard' style of the document sub createTextStyle { my $self = shift; my $stylename = shift; my $proto = $self->getStyleElement('Standard'); my %default_options = $proto ? $self->getStyleAttributes($proto) : %OpenOffice::OODoc::Text::DEFAULT_TEXT_STYLE; my %opt = ( %default_options, @_ ); return $self->createStyle($stylename, %opt); } #----------------------------------------------------------------------------- # set a page break before a paragraph sub setPageBreak { my $self = shift; my $p1 = shift; my $pos = ref $p1 ? undef : shift; my %opt = (position => 'before', @_); my $element = $self->getElement($p1, $pos) or return undef; my $stylename = $self->getStyle($element); unless ($stylename) { warn "[" . __PACKAGE__ . "::setPageBreak] " . "Element has no style\n"; return undef; } my $style = undef; if ($opt{'style'}) { $style = $self->createStyle ( $opt{'style'}, family => 'paragraph', parent => $stylename, category => 'auto', properties => { 'style:page-number' => '0' } ); unless ($style) { warn "[" . __PACKAGE__ . "::setPageBreak] " . "Style $stylename creation failure\n"; return undef; } $self->textStyle($element, $opt{'style'}); } else { $style = $self->getStyleElement($stylename); unless ($style) { warn "[" . __PACKAGE__ . "::setPageBreak] " . "Element style not found\n"; return undef; } } if ($opt{'page'}) { my $name = undef; if (ref $opt{'page'}) { unless ($opt{'page'}->isMasterPage) { warn "[" . __PACKAGE__ . "::setPageBreak] " . "Style element is not master page\n"; return undef; } $name = $self->getAttribute ($opt{'page'}, 'style:name'); } else { $name = $opt{'page'}; } $self->setAttribute($style, 'style:master-page-name', $name); } else { $self->styleProperties ( $style, ('fo:break-' . $opt{'position'}) => 'page' ); } return $element; } #----------------------------------------------------------------------------- # removes a page break from a paragraph sub removePageBreak { my $self = shift; my $element = $self->getElement(@_) or return undef; my $stylename = $self->getStyle($element); my $style = $self->getStyleElement($self->getStyle($element)); unless ($style) { warn "[" . __PACKAGE__ . "::removePageBreak] " . "Element style not found in the active document\n"; return undef; } $self->setAttribute($style, 'style:master-page-name' => undef); my $prop = $self->getStyleNode($style, 'properties'); $self->setAttribute($prop, 'fo:break-before' => undef) if $prop; return $element; } #----------------------------------------------------------------------------- # get/set the style of a text or image element sub style { my $self = shift; my $path = shift; unless ($path) { warn "[" . __PACKAGE__ . "::style] Missing object\n"; return undef; } my $element = undef; if (ref $path) { $element = $path; } else { $element = ($path =~ /^\//) ? $self->getElement($path, shift) : $self->getImageElement($path); } return undef unless $element; my $value = shift; my $namespace = $element->getPrefix; unless ($namespace) { warn "[" . __PACKAGE__ . "::style] Unknown class\n"; return undef; } my $attribute = $namespace . ":" . 'style-name'; return defined $value ? $self->setAttribute($element, $attribute => $value) : $self->getAttribute($element, $attribute); } #----------------------------------------------------------------------------- # get the style name of any content element sub getStyle { my $self = shift; my $path = shift; my $element = ref $path ? $path : $self->getElement($path, shift) or return undef; my $fullname = $element->getName || ""; my ($namespace, $name) = split /:/, $fullname; my $style_attribute = undef; if ($name eq 'master-page') { $style_attribute = $namespace . 'page-master-name'; } else { $style_attribute = $namespace . ':style-name'; } return $self->getAttribute($element, $style_attribute); } #----------------------------------------------------------------------------- 1; OpenOffice-OODoc-2.125/OODoc/Image.pod0000644000175000017500000007111411140647616015320 0ustar jmgjmg=head1 NAME OpenOffice::OODoc::Image - Image manipulation methods =head1 DESCRIPTION The OpenOffice::OODoc::Image class is a derivative of OpenOffice::OODoc::XPath designed for the manipulation of graphics objects contained in documents. It mainly allows you to modify the size and position of an image and exchange its content outside the document. This class should not be explictly used in an ordinary application, because all its features are available in the OpenOffice::OODoc::Document class, in combination with other features. So, each time an application needs to get an image-focused access to a document, it should use the general odfDocument() constructor instead of the odfImage() one. Practically, the present manual is provided to describe the image-container processing features of OpenOffice::OODoc::Document (knowing that these features are technically supported by the OpenOffice::OODoc::Image component of the API). Knowing that an image is displayed or printed according to a style, the OpenOffice::OODoc::Image features should be used in conjunction with the OpenOffice::OODoc::Styles ones. The OpenOffice::OODoc::Document class allows the user to invoke text-, style- and image-focused methods from the same object. All the methods described here can equally be used with images contained in style sheets (headers, footers) as with images contained in the body of a document. It can therefore be associated just as well with a "styles.xml" member as with a "content.xml" member of an OpenOffice.org file. This class works with all types of document (text, presentation, etc.). For all methods where the first argument is given below as "image", it is (unless otherwise stated) either the name of an image as it appears to the end user when editing its properties in OpenOffice.org or StarOffice or the image's element reference obtained previously by the program. All these methods fail and return a null value (or in some cases produce an error message) if the argument does not correspond to a known image contained in the document. Note: This module is not an image-processing tool. It can insert or remove images, and control the way the images are displayed in the documents. But it can't process the images themselves. =head2 Methods =head3 Constructor : OpenOffice::OODoc::Image->new() Short Form: odfImage() This constructor should not be explicitly used in ordinary applications knowing that all the features of the returned object are inherited by any Document object. See OpenOffice::OODoc::XPath->new for commun arguments. The XML member loaded by default is 'content.xml', but only member => 'styles' is required if you want to work with page background images. Example: my $doc = odfImage ( file => 'my_presentation.odp', part => 'styles.xml' ); Real applications should not expressly use this constructor, knowing that the compound OpenOffice::OODoc::Document (whose usual constructor is ooDocument) inherits all the features of OpenOffice::OODoc::Image. =head3 createImageElement(name [, options]) Creates an element which represents an image and inserts it into the document according to given parameters. The image element created is only an anchor. It then needs to have a graphical content loaded into it and, if needed, be given parameters using other methods. Example: $doc->createImageElement("Logo"); inserts a default image element called "Logo". Normally, the image name is unique in an OpenOffice.org document but no checking of its uniqueness is performed here. Things you should know, however, are: - if several images have the same name, one only of them can be retrieved and handled by methods which use the name to identify them. It is almost impossible to know which of them it will be, without a thorough knowledge of the OpenOffice.org format and the internal logic of OODoc. - if the document is subsequently read and saved by OpenOffice.org (which controls the uniqueness of image names), sequential numbers will be given to all but one of the repeated names in order to make them unique. Attributes can be passed in pairs [parameter => value]. Such as: style => image style name It should be noted that an image should normally be associated with a graphic style. The presence of a style is not obligatory when calling createImageElement (and this is not checked), but the image should preferably be actually linked to an existing style before displaying or printing the document. See OpenOffice::OODoc::Styles for style creation or, better, OpenOffice::OODoc::Document for image styles. attachment => anchor element indicates if the image is attached to a text element (for ex. a paragraph), and which one. This parameter must be an existing element reference (obtained, for example, using getElement or selectElementxxx). It is useless if the image is linked to a page. The OASIS OpenDocument specification doesn't provide the list of possible attachments for an image, and OpenOffice.org make some differences in this area between the different document classes. For example, OOo Writer doesn't display images which are directly attached to table cells, while OOo Calc does (in text documents, an image which appears in a table cell is attached to a paragraph, possibly empty, belonging to the cell; see the example below). page => anchor page If this parameter is used, it indicates that the image will be anchored to a page, and the given value is a page number. It does not matter if, when createImageElement is called, this number is beyond the end of the document or not. If the content class of the document is "presentation" (Impress) or "drawing" (Draw), then the page option is mandatory and must be either the visible name or the element reference of an existing draw page. In a "text" content, all that matters is that the particular page exists when it is opened by OpenOffice.org and if this parameter is absent, the image is anchored to a paragraph. position => coordinates This parameter indicates the x,y coordinates of the image in relation to its anchor point. By default and generally, if the page parameter is given, the origin (0,0) is the top left corner of the physical page. When attached to a text element, if there is no given position, the image is appended to the text. Coordinates go from left to right and top to bottom, however everything really depends on the image style. Coordinates should be given here in the form of a string "x,y", and the default unit is centimeters. You can choose millimeters instead by attaching the usual abbreviation, such as "12.5cm, 35mm" which is the same as "125mm, 3.5cm" or "12.5,3.5", etc. The point ("pt") unit is allowed as well. size => size The image's size (width, height) is given here in the same syntax as for position. Caution: if no size is given, you must not assume that the image will be spontaneously displayed in the document in a convenient size. Remember that the "original" size of the image is not automatically selected by default; the application must provide a display size. If an image is to be displayed according to its original size which is not known in advance, you can get it using, for example, the imgsize() function of Image::Size (this function returns the size in points, so the "pt" unit must be expressly selected unless you convert the size in centimeters). description => label This optional parameter gives the descriptive text (long label) for the image as it will appear to the end user when editing the image's properties. link => link The link parameter contains a reference to the image's physical content which can be inserted into an OpenOffice.org file (internal link) or a reference to an external file or even the URL of an image accessible using a communication protocol supported by the OpenOffice.org suite. Remember that, with OpenOffice.org, physical images are "referenced", and not "loaded" into XML elements. The reverse of course would not only be against "good practice" but would result in outrageously large documents. According to the OpenDocument specification, the conforming applications could embed base-64-encoded graphics in the XML elements, but this option is not used when you insert an image in a document through the OOo GUI. If the target link is unavailable when the document is displayed, an error message is displayed in place of the image. (See imageLink about links.) This parameter is useless if import is given. Relative paths in the local filesystem are allowed, but should be used with care, due to differences between operating systems... and between editing applications (for example, in a Unix environment, OOo 2.x requires "../image.jpg" while OOo 1.x allows "image.jpg" for a link to an image file in the current directory). import => image file The presence of this parameter indicates that the image content should be imported from an external file and, implicitly, that the image link (which it is useless to give here) points to the image imported into the OpenOffice.org file. The import will only be made when all the updates are validated by a save(). See importImage() about importing images. An imported image is *not* embedded in the XML image element; it's always referred to through a link, and stored in a separate, non-XML member of the ODF archive. However, with the "import" option, the appropriate internal link is automatically created and the new member, containing the image, is automatically built later, when the save() method is executed from the current document (provided that the image file is available). For those who know the appropriate XML vocabulary, there are other parameters you can pass. Parameters other than those described above are written to the image's XML element as is and without any checks. All of these attributes, and others, can be read or modified later by other methods. We would discourage you from relying on the default choices in a serious application, but it is still a possibility. With no parameters (other than mandatory image name), the createImageElement method chooses its own often arbitrary course according to the following circumstances: - if the OODoc::Image object is associated with a document body (document-content) then a new paragraph is created at the end of the document and the image is inserted into this paragraph (appears at the end of the text). Repeated image creations without parameters will therefore add images one after the other at the end of the document. - if the OODoc::Image object is associated with a background (document-styles) then it tries to create a paragraph in the first available header and insert the image into it. If no page style contains a header, the same is attempted in the first available footer. If there is no footer either, the creation fails with the message "No valid attachment". This method otherwise tries to be "intelligent" whenever the set of parameters is incomplete. If the results are useful, then the rest is up to you... The method returns the new image element's reference (undef if it fails). The following example attaches an image to a paragraph, gives it a size, loads its content into it from an external file and attributes a style to it which has been defined elsewhere. You will note that this example combines an OODoc::Image method (createImageElement) with an OODoc::Text method (getParagraph). This means that, here, the $doc object is a OODoc::Document class. See also createImageStyle in OODoc::Document. $doc->createImageElement ( "Landscape", description => "Kilimanjaro in winter", attachment => $doc->getParagraph(4), size => "5cm, 3.5cm", style => "gr1", import => "C:\Images\Landscape.jpg" ); The same image element could be inserted in a table cell. To do so, in a spreadsheet document, the "attachment" option could be set with a $doc->getCell($table, $row, $column) value. But the present version of OpenOffice.org doesn't allow direct cell attachments in text documents; the image element must be attached to a paragraph which is in turn attached to the target cell. So a possible approach consists of 1) issuing a setText($cell, "") in order to ensure the target cell contains an empty text paragraph and 2) provide an "attachment" option set to $doc->getCellParagraph($cell) in order to anchor the image to this paragraph. Remember: creating an image element will only make that image appear in the document if 1) the image has a valid link which points to a valid image, 2) the corresponding graphics file exists (loaded possibly using importImage) in the archive if it is an internal link, and 3) the image has a style actually defined elsewhere (either pre-existing, created using the createStyle method of OODoc::Styles, copied from another document using replicateElement, or coming from another source). =head3 exportImage(image [, destination]) Exports the content of an image contained in a document if the OODoc::Image object was linked to a file when it was created (with file or archive parameters passed to the new constructor). The first argument is either the name of the image (as it would appear to the end user in the image's properties in OpenOffice.org), or the image element's reference if the program already has it. The second optional argument is the destination file. Example: $doc->exportImage("Logo1", "C:\My Documents\logo.jpg"); It is up to the application to choose an appropriate extension for the exported file (.jpg, .gif, .png, etc.). You can easily find out which extension using the imageLink accessor. Without the second argument, the image file is created in the current directory, and its name is the name of the image in the document, with an extension depending on the format (.jpg, .png, etc), according to the information stored in the document (but the format is not checked). Be careful, the export fails unless such a construct provides a valid file name for the operating system. If the image is unnamed in the document, exportImage() tries to build a path/name which replicates the internal path/name of the image in the archive and to use it under the current directory (this path normally begins with "Pictures/"). Caution: this method only exports what is exportable i.e. internal images (physically contained in the file). It has no effect if used with an image inserted by an external link into the document. =head3 exportImages([options]) Exports all or part of the images contained in a document. By default, and with no parameters, each internal image is exported to a file whose access path is the same as it would be if using exportImage. This behaviour can be changed by parameters passed in hash form (parameter => value). Possible parameters are as follows: selection => filtering of image names (regex) filter => filtering of image names (regex) name => filtering of image names (regex) target => path and/or basic filename path => path and/or basic filename suffix => extension to be given to filenames extension => extension to be given to filenames start_count => begin count indicator The "filter", "selection" or "name" parameters allow you to export only those images whose names match the given regular expression. Such filtering works on the name as the document "knows" it, i.e. as it appears to the end user in the image's properties within the document. It is not a technical filter and does not allow, for example, selection of images according to their file type. The "target" or "path" parameters allow you to choose the access path and basic filename for exported files (e.g. "/usr/local/images/img"). If given, a sequential number will automatically be added to the basic filename to identify each file. The "suffix" or "extension" parameters allow the application to force a common extension for each file instead of leaving the extension as it existed in the archive (which normally identifies the physical image type). By default, the sequential numbers given to filenames (between basic name and extension) are reset to zero each time exportImages is called. An application can however force the numbering to start at a different value using the "start_count" parameter. In a list context, this method returns a list of exported files which the application can use later. In a scalar context, it returns the number of exported files. =head3 getImageElementList() Returns the list of all image elements in the active context. If the current OODoc::Image object is associated with document-content, this will be the images contained in the body of the document. If associated with document-styles, this will be the images linked to headers and footers. =head3 getImageElement(image) =head3 getImageElement(element) Returns the element which corresponds to the image whose name is given as an argument. This name is usually unique as OpenOffice.org does not allow the user to give two files the same name in a document. It is the name which appears in the "Options" tab when editing the object's properties in OpenOffice.org Writer, or in the "Name object" dialog box when you right-click an image in OpenOffice.org Impress. Returns undef if the image is not found. Can also be used as a check method by specifying an element as the argument instead of a name. In this case, the given element is simply returned without modification if it is indeed an image or undef if not. Caution: images do not always have names. OpenOffice.org Writer gives default names to images (e.g. Image1, Image2, ...) if the user does not deliberately name them. This is not the case in Impress. =head3 getInternalImagePath(image) This method returns the given image's link (see definition of "link" in the section on imageLink), but only if it is an internal link in a form which is directly usable by a zip archive management tool (without the initial "#"). =head3 imageAttribute(image, attribute [, value]) Accessor which allows you to check or modify (even create) an image's XML attributes directly. The attribute is modified or created if a value is given as the third argument. If not, it returns the current value of the attribute if found, or undef if not. The name and value of the attribute must be given according to the OpenOffice.org vocabulary. This generic accessor remains invisible to most applications as specialist accessors are available for the most useful attributes (e.g. imageLink, imageName, etc.). =head3 imageDescription(image [, text]) Returns an image's description, or if the "text" argument is given, replaces it. This description corresponds to the optional text which appears in the Options tab when editing the image's properties in OpenOffice.org. It is not used in Impress or Draw documents. =head3 imageLink(image [, link]) Reads or modifies an image's link. A link is the address of the file which physically contains the image. If working with an image loaded into the document (often but not always the case), the link is internal and the file is physically stored in the zip archive containing the document. In this case, the link is written as "#Pictures/xxxx". It corresponds to what the user would see if opening the file using a zip archive tool instead of opening it in OpenOffice.org. If working with an external image, the link is then the URL of the image. The same method allows you to read and modify the link. If a second argument is given, it cancels and replaces the existing link, or creates a link if the image was still "empty". Example: $doc->imageLink("Logo", "http://www.mysite.com/logo.png"); Caution: the actual physical existence of a valid image which corresponds to the link is not checked. =head3 imageName(image [, name]) Reads an image's name, or if the second argument is given, replaces it. Returns undef if the first argument (name or reference) is not an image. =head3 imagePosition(image [, x, y]) Allows you to read or modify the coordinates of an image in relation to its anchor point. Coordinates are returned in the form of a pair of values. Example: my ($x, $y) = $doc->imagePosition("Landscape"); If the coordinate arguments are given, they replace the image's old coordinates. Caution: coordinates are not numeric values in the classic sense of the word. They are character strings starting with numeric values and ending with units of measure (normally "cm" or "mm"). If an application passes only numeric arguments without giving a unit of measure, the default unit is assumed to be the centimetre. Example: $doc->imagePosition("Landscape", "2.5cm", "5.125cm"); Normally, coordinates are measured from left to right and from top to bottom starting from the point of reference. The point of reference is normally the top left corner of the page or paragraph to which it is anchored (depending on the type of anchorage). All this can depend on the page style. =head3 imageSize(image [, height, width]) Allows you to read or modify the display size of an image. Returns the height and width as a pair of values. If height and width arguments are provided, they replace the image's old size. Note that this method deals with the display size only, and ignores the original size of the image; in other words, it doesn't change anything in the graphic object itself. Caution: it is up to the application to maintain the aspect ratio of the image if needed. See the "size" option about the createImageElement() method. See imagePosition() about measure formats. =head3 imageStyle(image [, style]) Returns the name of the image's current style, or changes this style if the second argument "style" is given. In the second case, the presence or pertinence of the called style is not checked. This can otherwise be created or imported later. =head3 importImage(image, filename) Loads an image's content from an external file. Example: $doc->importImage("Image1", "C:\Images\Portrait.jpg"); This import cancels and replaces any previous image content, if any. The first argument is either the image's name (as the user would see it) or the image's corresponding element reference. In any case, the image element must already exist, created if necessary using insertImageElement(). The second argument is the full path of the image file to be imported and associated to the ODF image object. If this argument is omitted, importImage() assumes that the full path of the needed resource file is indicated by an external link already associated with the image object in the document (see imageLink). Obviously, the imported file should preferably be in a graphic format supported by most office software products, but this method actually allows you to import anything, including data which could not be displayed. Caution: this method does not carry out the import immediately and does not check for the presence of the file to be imported. It only sends the request to the associated OODoc::File object (via its raw_import method) which performs the operation at the next save() call. As a consequence, the image file is needed later, but its availability is not required by the importImage() method itself. =head3 insertImageElement(name [, options]) See createImageElement. =head3 isImage Method added to OpenOffice::OODoc::Element class objects by the OODoc::Image package and is an element method rather than a document method. Allows you to "ask" an element if it is an image. Example: print "This is an image" if $element->isImage; =head3 removeImageDescription(image) Deletes an image's optional image description. =head3 selectImageElementByDescription(expression) Returns the first (or only) image (if found) which has an image description matching the provided regular expression. See also selectImageElementsByDescription(). =head3 selectImageElementByLink(link) Allows you to select an image element by its link (as in imageLink) instead of by its name. The link can be an approximation and represented in this case by a regular expression. It should only be used if you are sure that the link is unique in the document. This is not normally the case, as many images can share the same content. Conversely, the returned element is the first image, in the physical XML order, which has the given link (and not necessarily in the logical order of the document). Returns undef if no image has the given link and therefore allows you to find out if a particular target (e.g. URL) is used in the document. Note: if an application needs to get a list of individual or multiple images which share the same target, the easiest way is to loop through the list returned by getImageElementList() and perform an imageLink() on each element. See also selectImageElementsByLink(). =head3 selectImageElementsByDescription(expression) Returns a list of images whose image descriptions match the given regular expression. =head3 selectImageElementsByLink(expression) Returns a list of images whose internal or external links match the given regular expression. Allows you, for example, to get a list of image elements which share the same physical image file or even those which use a particular transport protocol to access these images. Example: my @webimages = $doc->selectImageElementsByLink("^http:"); allows you to get a list of images referred to through an HTTP URL (i.e. which will not be visible if the user's machine is not connected to the internet). =head3 selectImageElementsByName(expression) Returns a list of images whose names match the given regular expression. =head2 Properties No variable is exported. There is a static class variable which contains the description of a default image style (in hash form): %OpenOffice::OODoc::Image::DEFAULT_IMAGE_STYLE This hash is not used directly by the module itself (which offers no styles functions), but it is available to any application which needs to create image styles using either XPath base methods or the Styles module. Its structure is exactly the same as the parameters hash expected by the createStyle method. By default, it is automatically used by the createImageStyle method of the Document module. =head1 AUTHOR/COPYRIGHT Developer/Maintainer: Jean-Marie Gouarne L Contact: jmgdoc@cpan.org Copyright 2004-2009 by Genicorp, S.A. L Initial English version of the reference manual by Graeme A. Hunter (graeme.hunter@zen.co.uk). License: GNU Lesser General Public License v2.1 =cut OpenOffice-OODoc-2.125/OODoc/templates/0000755000175000017500000000000011416644377015573 5ustar jmgjmgOpenOffice-OODoc-2.125/OODoc/templates/template.odg0000644000175000017500000001510211320701520020052 0ustar jmgjmgPKP%<.++mimetypeapplication/vnd.oasis.opendocument.graphicsPKP%<~6c content.xmlVM +Rsn՘R綧4U{E )@a:Y.1<_<p:6\p$0>gkH0u73t*?/W**QFYU(}k5t*b1Kا-t5"<:렦OU -YeY ـr7 F]dP+@,eD,6._؉@ ?j'2֙ib΃MLd9U68*s}G3ǷWjYC]gztBR-ovWz8B_{I5} GE}4+Aj-6m!`gS93ap ʕ|bM^1-4 aHkَtB@u%`TA0Z %:8 o EI-ASU",ِ:I9cUwntŖa@ D!x®*ܙTǣ{fVqW@ n&/ ]b0y= |sBEPʦܛWc".]UD:sJʚB3!wPKP%<-=~EJ styles.xml\r6+8̤7$'j%N'Ӊ!-+?c_@PDʔ"[vd&.݇.][儦3xӀ$]?~r._QD< iP$8N1-SK$(STS9ȇu.񮃅mm,wYCV] [Ѯ؉$Clyqff/9ϦZрO&Wj+.+X,1?]m`'lM"cΪf ` sL-nvsDsIz2 ͱ ˖t?RM^\¶UH9LemVjKw7vտ ^#3_ȖCG"qق,%zf/ʖ$.UbHʝA\h\J%ځ N FBL)%KH^ȵrV9Ik=XFPsHF/T}'!<>fkUD4jRS#F(αCYe;9v!tHOS؜VD{֟PF/wJX G; b@af3aW"FD7Lpq)Q(D)g˧/MSBT;+*k:GdWxlXrL{~wUeZ7y'Kao|EKq_ikE{Ic!J'CQmة6w 9GiXhwϥLiY_`,PU%<E&.S'ٖQehH o{ڬ5{ mqA%-_Y"&ҘIHE`1Ţ5*+p wPJiL S)s"̨kj`bj1ilT>k GM\_Uq؍T~N4?f C ~CuOu:Wز|ܒ|>h 4 AP<1@lGJ}{O|[RBM<2oEɃ[YǏEJ-& hLk Z"5KaO '{rcד=ݓ ~^6xg&Noϩgr[oy: vMT!Y 4|jblͭڪDQL&*]8 Pݮg$zF`3=#3 x^Sa$~ݾp7[UꐍP\Y{:T=Q;<C혳Fla_waǂ\SkvJ8T4'{MnF}j64"%l32\ _|U!p*&8|PI%Vol]Z*+քNpne1QF6G߄`le7;?N!-?Wg1;8G͂rC-ᜲPtNUmXD ʹu;/#Bg`&vfR+)]#5l\|4 eimO)yJ:F! BY4$%ժh(ac`{( K-o)[0%LU$V=;'u/^P%,6vdܼœ@ hY)vTG-97#8Q Œӓ7t޺^0jM946ȮQ JT1xE3m(#mE&[v ?Џ3%Or⫞S=RљL"A P}R6KϾ'NoyzpO~ے> @: 0H9=rCb\@LKJR/:AL3I1AoRʘ2(fcZǝWO4 - <^J D=3`O@tQ$Bol$/Lo>]K9?|¨ކݍdAti>ֱOM1\uƄ7fFT9JnD~>d3= Y2{K Ÿ=G9M1E=5ue#K_PKP%<2BNMETA-INF/manifest.xmlj0 }{m6t:Jj\ڷ_R֟m=! -y>XS1Փ()j7sQjt9(}.Fjv`jVZEz4iU!:޵U*>BiEJ1e5B r>MyǣsLK|4Hc-2LއmvnhCa\?7|޻wS<z>JbvB}D-dtc}Haܔ? {oIi2# 8My<%76gtn+cçxKO\}PKP%<.++mimetypePKP%<~6c Qcontent.xmlPKP%<-=~EJ styles.xmlPKĚ%<q meta.xmlUTOCKux PKP%<׃| Thumbnails/thumbnail.pngPKP%<' Configurations2/accelerator/current.xmlPKP%<Configurations2/progressbar/PKP%<PConfigurations2/floater/PKP%<Configurations2/popupmenu/PKP%<Configurations2/menubar/PKP%<Configurations2/toolbar/PKP%<*Configurations2/images/Bitmaps/PKP%<gConfigurations2/statusbar/PKP%<`/ # settings.xmlPKP%<2BNMETA-INF/manifest.xmlPK&OpenOffice-OODoc-2.125/OODoc/templates/template.sxw0000644000175000017500000001601411014071105020124 0ustar jmgjmgPKX819mimetypeapplication/vnd.sun.xml.writerPKX8Configurations2/statusbar/PKX8'Configurations2/accelerator/current.xmlPKX8Configurations2/floater/PKX8Configurations2/popupmenu/PKX8Configurations2/progressbar/PKX8Configurations2/menubar/PKX8Configurations2/toolbar/PKX8Configurations2/images/Bitmaps/PKX8& -s content.xmlo0QCCɀjRUiR^7l8iK$ziߏ{ya~Ecp%t<#&\n6[Fse*RLڄ(iץI"Z[Io+AWx؃ ]3Tj3說յ# у9zyw +J-E bՂ4OQke.[Fpۜwf [VkzFkjvۣ-zC@h^ gRjy7@ 4Lwp2,HQ=" % `]pq u"[Kcl82m M%ΙIپ`ӎhRh.@*U[T˝5 F"^Xf%EPFc4woAks"~[c)Od8D?X /P;ɆIP fG{wÚmw>o@)Mō0=_hS"`,/ؾB9:5pBIܘ=O [‡| r&M!!QK rǾBjWBϭz_ptA@=B+lk//?PK& -s PKX8<@ styles.xmlYQo6~04lo,',I1l(6C {%b+INHeItp xI߿dCb?tL(OD!6xսXXBRT:T)j_sugAuyE.M\Glٿ( \ӝE#npT( _ nAƋ"譐rƿ x\Yܠ$9x(ƑTQ*TohpYym3H2"cM#J"w&/UDrs q!FzzSV2Me '>HM"K0 Uqj y%S '&Id q0Y XOND5z_t;CM?R_:0LZ->\SN%I(N7" Хzyĸ2iwSM>҂4-UءvLTy=uک$eHj|}2m!MpGj`!tj$0 LH&9B緣 B)OOzis0$D e3g-%%0i'Ƅ r/TGK:lhCj>KD.`iYцU},儯+4 TC PɍQ b;lzS9Nusڐp{&(sswziZ/3k!,J=G]=X%$}" SQN40'i A5؂3)UV<ѕU1(97㜋D2F&nh~Z)<2Ѥ^t5=zz&@bOgPYU?PFxĂn I/`9b;R_(-C-Tgx<;jۚ%G4l.9Q |ûR4JVj >ڀO0YϭhDB7x8f5K5aZde: ?4nMQ|[5*8*ipdr2svlbY9P'd/>kW f+%P`sS1a68;}apeptl7]K{>iekO8^0Qel̈Jۣ[tCz ZpPʼ=vC{-G9 e6[Ȑ3_WWEzus^H~Hn/ҫEz_:,X T+xC>mH^Q3:jJ6ZN6>S}u18ӜaδTfanh%EhZI霮t-c<p{oF_}%y* JSwn)xhڲrϦ '([gx_L_)fpNHY!t=H;EY4Hytn<@ҩјMgmcSsGL<{0d/D}~ɶ*ѥL6kjir§{d7jْDܳj}ܦ?oxͳ֕4T4gruA%,"8׎v{'j,cxmm[$wabv6>C3%~qU^o4>5.W0|!gLNAo(# `kzdK\#Pa„j*!, [z)}2[rE<'FQ{M?PzQ|^edUyњYK0".zPTN`T| 1͐7[Vtn፫bD; ŽРkf# 䐑ڮ5]]#Qw~03FbePXM;%H8P?%JjRu좕Ut(6>sGS :\iC4ҵ86Fm+b8)Q~N!Љ%ɢ!0,eu $t*RxM&*cae_!ԧ8MMLçL-"sdS&Y: 6+p)Rjg:q$j_-䫦Kf"HDJ.—MWJ@bY8[p:E^S0͓6W@.($]YDZX>kP07*:tqC?l ԧk}8f>E+:y?(|͙'20TJ SUڂǾ^L  |QTu2%jW>m'Jѵـ`j1Q.eb=>i$ 랴U#3R)cItآ b]CP#X$7S*5D{訁Ӻ1}>q.y͖xiO*8pm*cw2U1mveݘBSR7lO6,jy:6Goum;CpZFEkt0pf\iLxp^C>2:Kt7g$~gZq!P̌ovc(U[;~PKuT2iPK8umeta.xml OpenOffice.org/2.4$Linux OpenOffice.org_project/680m12$Build-9286OpenOffice::OODoc Text TemplateOpenOffice.org 1.0 formatTemplate for the Perl Open Document ConnectorOpenOffice::OODoc Project2008-05-18T20:15:33OpenOffice::OODoc Project2008-05-18T20:15:330PT0M0SPKX819mimetypePKX8DConfigurations2/statusbar/PKX8'|Configurations2/accelerator/current.xmlPKX8Configurations2/floater/PKX8Configurations2/popupmenu/PKX8/Configurations2/progressbar/PKX8iConfigurations2/menubar/PKX8Configurations2/toolbar/PKX8Configurations2/images/Bitmaps/PKX8& -s content.xmlPKX8<@ styles.xmlPKX8׃|[ Thumbnails/thumbnail.pngPKX8t~=  settings.xmlPKX8uT2i.META-INF/manifest.xmlPK8umeta.xmlPKOpenOffice-OODoc-2.125/OODoc/templates/template.ods0000644000175000017500000001450311320701225020074 0ustar jmgjmgPK8%I*Y[fŅͷu-(~?,#Q0ңJNA fr#sTRгS,ܮyRYkfczvyy<[L5+@_p?^>Jsc9YmtWJEG{o{-,o9?˚Sf+՝ZmLL5bV1JYf=GRԝr(N Ƕ"phA GdUwTmܹ y&W2Ƌ,4+T)K+SJq% N:C & <)խ-4l` c!NNOW֘ V &n: OvʵwO xW 6uY?$s1k@vu&b߁LVb}#DNutc|X;\DMJ hM%g܁p/޽_;8 $)޺˦w0kMZ2x:i'l-|´eGwy'Uޤ&vvjZ[UL+I-=ѸcOoTyq+!^Sl/I*̭A:j7_'I^wbi.m3<Mhq\(? %I1c_L9.NO_@ףz&d5M?CPK8%ـx4a&fuq:_FAo S*CMqA62R! PޮK J)'H#X7>UV{ʬ`KC%\-d^ #*>1r ݝ! f5>,O|3mZ`KSuS}1Q_>0*RGޜD@Rr@28n?4MזQdW$xϙsZI 5ԒQ {*dG~c_2jddN5o+H@?P&PMDEgho8&dj֪s-~a)l}"ϒg7'fٔ͠OB_[xm5쫄W}Ok@$32sa2JȎrB`#VRhmaRn`ru.?*N~}rޓ1/y>_δ*Zq86R0Za$# ;XxBS 98^0èxCY#±rc~TM3##H:H͵#4!@2didLўuUS) <5QNRTx)ĭ 6DŽ1QF)鳔~Ja Q눗@p !$TfgIeӗ 3NeͰ2w,ϳ*PwEpB4( p%GK)q{wk+$="NDsXVܓ_5U.9JCꋋf5:>^W"؋2㦑 -t;X7^sA~Icw=!owg2p9Ӭ4NA]~~Eɺy7FAk1ģ="tf$M$p\I˕* \tL˧sqp0RAդ^,\^מ:GG]?u/PKa%<A'meta.xmlUT CKCKux SMo0WXWRjeJD͛ĭmϘBvяdOZ?`jфDוP}ZwSZetXy[rq D^,Qkӥg5J؜BRܤP\0^uhs4MSnGj'^X K&ܮWKuy%!nM G|mutoy$UZ߁;÷V*Nk;. 'a9zi OAd۷.%/ndAFJòwj\F4P‰Rܧ-(~q~㻘vW ]cAOB~r'(xݬ7 7 1׭rE=A#'Dq㜎?;_PK8%`tm&“Mrg"I GL?OPh2jA.?%"C'kK(C-%G'3LV ''k)IͿNs Ⱦڤ8e-ʑfxXD}aFZR"6=D:0ӇL [5J̚= q [\t$M?dy 񝤬u"sAKT*Sv4%'"؏K_)0aމrs$9A8wXbV̇}毤E+ٗӺH0Kq6[%WTgʽb2)j`X& |W6l<{4P p9R~(L1e1LÃoe~sO5K:4AȮL!*!Ɍ jp- 5 L..`< Nw#|ݑ)@.!ÐLd-O4 9."c Q m$qɺe_]l[á8&#[ГbrRKM%3"=&7*Sxg㻳y LwSr9 u ZL+j Z4CBC 8d 8%Gȣǯ,)&WBVҙz7l[zѰ 3ku0odSS>8QZɌLc^&Ȍn^Zzn]?yc97v7`їߤ)WEBQySaBS ef'j!VTQtd1E ^(#_6e%N-m7跡OPK8%<ɇ6CQMETA-INF/manifest.xmlj0 }{m6t:Jj\ڷ_R֟m=! -y>XS1Փ()j7sQjt9(}.Fjv`jVZEz4iU!:޵U** "+KƔx)CNˡ41aN. ͎0z%u I>UpYxuOxERhpX(Uq%>"I!ySHv0z$Bߎ,lb4Q؜Uҕuy32e4/oqPK8%4> W7fwb/ &Nfe@.nnnGMW)Y|8M5$uelg$R!9'As?Y~*&-aӠKb"1Fdz%7)vp1COaG$0p ǝM7PKʬ5 PK8rETp~ styles.xmlY_o6Thɒ&"C;i7앦(+% e;}٧'ّiʖnh ݏǻzW`CDCyIn'3LyW %rޟk;\ Q #&N'il%hg-WD$^ HY]r7P&G$^#1U,'Og# gkP?^'UŇ=7s>~~p6=V㭠G1HԤ1 "QiB. :@F0k@\ ]O"979hTDPҦ!j*1du`:񽛿$-c7/<;uHR~WК!+Dv&pKj&$+D,¤YS$ü2HTk"Lq?ba*ZT,Zy[I-PCQunv,ngUExE-c^֌:aӦU<@Ni]KґWIV6@a<`B}7i$6 g*EKQGءw{[ ~G}w3k_F?rV:6qÝ ËNG?&2U*f ֒ϞU&k֑*7" ~>g89fq^K*<=ģoI3Hdt{CRnڀEmo+u=xԵ%Xgëa܂`}?o&'L=ʎ=LPߪ_v! BMs9iVE8n5]7щ cbO?)xKhnTY,y2ࠛݘmnJ!%^Y78t4?L]RoA;*yp&"R>Iuuz2y6ezOv2pKj.F0=#”{F GhӾ3 zC99@aOYG)<:ҋ89W?ܜu^)Ѥl4HUό|+T9hPKrETp~PK8plLd{Thumbnails/thumbnail.png sb``p ;8؀,{{8Ty{ɐ٧; }rX,vW/Ȫc?ͦ~9&͓ .:il9*8*8X/~dwM=]\9%4PKplLd{PK8y j settings.xmlYmW:=JA׻x=BQmyIR M)xJy¹~6yf&y2 11zeTNLiBDWFK}(e/_Іh8ZYmccDC8a &e+RO3ṀuYHèBË-HU kl? c1]sx $զy.>8zeTF;*ǃ\@7JΛE4?q@18 V daq+NQړYc+ &%#ҦeG=s؀!LJAKZg C@1\l# HfSv댄 nA3]a 8kMy(+S:t8LOE X g3_'7F(La'՟f<Y*Su?۪kSFLÒ64B#Y0ůX6S3,\?ʥ 2 iL3ʆCʝ*Ydi7C4FЕB,wd@ #(z,hL EmBfr˚! ]mom@MI/-&CPZ[D_zQ$,nO?~_|uaXam}KfX=s+CA;ނr;C/| YQFo)!7a}_̰ [/ڷ;Gjz,ԐjO5iC<^לoi! 8[&55&ԙ>iq8x5'6F>qFza}P%_vX{m*WUxHALlxp~܏5?QjҁwA;<3QuCG߁z6#"٠lZډi c.|_\1EJqBQVۀk:1:p(~*f+o>|'PKy jPK8YgMETA-INF/manifest.xmlk0.!v?mzÎ1}4 ɋ8VsKI?ߗUF_XZJdC~7bz]γkYaWdq`aQ/Z ,kfY͋6e{TTT5cSڟ%!9^ݤeg s:JKƭURp tC `WTn9mK .J^]&d?OpB84Dp.e&JP+ `[!LЧ֓dJ˖7a"'͸Å{Y_m.6ͥ@]YX\"Qvg䣸PKYgPK8P̀meta.xml OpenOffice.org/2.4$Linux OpenOffice.org_project/680m12$Build-9286OpenOffice::OODoc SpreadsheetOpenOffice.org 1.0 formatTemplate for the Perl OpenDocument ConnectorOpenOffice::OODoc Project2008-05-18T20:15:32OpenOffice::OODoc Project2008-05-18T20:15:320PT0M0SPK8EmimetypePK8BConfigurations2/statusbar/PK8'zConfigurations2/accelerator/current.xmlPK8Configurations2/floater/PK8Configurations2/popupmenu/PK8-Configurations2/progressbar/PK8gConfigurations2/menubar/PK8Configurations2/toolbar/PK8Configurations2/images/Bitmaps/PK8ʬ5 content.xmlPK8rETp~ ~styles.xmlPK8plLd{& Thumbnails/thumbnail.pngPK8y j settings.xmlPK8YgMETA-INF/manifest.xmlPK8P̀mmeta.xmlPKOpenOffice-OODoc-2.125/OODoc/templates/template.sxi0000644000175000017500000002237611014071105020116 0ustar jmgjmgPKe8Xmimetypeapplication/vnd.sun.xml.impressPKe8Configurations2/statusbar/PKe8'Configurations2/accelerator/current.xmlPKe8Configurations2/floater/PKe8Configurations2/popupmenu/PKe8Configurations2/progressbar/PKe8Configurations2/menubar/PKe8Configurations2/toolbar/PKe8Configurations2/images/Bitmaps/PKe8jY  content.xmlVAs*WxNo=%3{3b@4l HX#iX>]i؈ŞiÕ&yLLE~KO77U*5Lڔ(iwǥ)6m Td0eJWh\.QX'c<#eG;vƻN5>̢1Kч!;=2||D?^7c9 .NnD\iQ &KAy0gcv,fW4nkNTmb/ȁ pi4:{ڽ" '_(4{2pcɅ(]O]٥ !=poPTEeZ3^*_g;|kbXc`[+,X ~=8ug[ݷ'l4Cܠog, H߇Ki_\+u>$[~s :xwoG/g¬Z6Yg/wZe/n} d/u78|Ncm$@>{"*uLl#Z|yG!4V+0aw]+ Ō9wf-̱Iܨΰ,0K,Pqy)PKjY  PKe8V1 styles.xml]ݒ:S:{=lSU:n Mx<{~I~,~&U-u_o} EK2Lm#y~YjAk{-Z}{݇0J$} `2£u.mck2PۦiZ6n^I ؾ{r ^ غ'a] ö!t1 d^GO͈{#p-R.w_WJF" TCBg84a@auc+b.1J6zOj\ë}3Pڎu+ Xj!$=v{/O ,8oQ<ך8 AJHCK}1NąΈ=!`&0 VI?-þioZi?mùigğܴw=q7'soWv|O0=P}7?ajϳj z1 0iG6dli3~S8s$ǀ$N%:KPӅ?u@Z_.W{Ŵ #pm%Sg&Zj"IXzrtHfԜ(x@=r*a*a*a*aǰOkOy-)0nnΊ~'@_RmcqYU"6r~D~"ly3L+inI.j[Nj;n\'䪴yQ+Ή;i]G,ڵ;hظZގ鉴 Vu#HӶ;EFV{w"jw~gڭhw~"޵ϴ[Dڝծe6 ʺwKx iGM[q 0آ(_N/$~Dfw΄ao_Ě'u| 6.f`cagQ[gZlgJ|gej8sr-6B>jG7?.~p|֝|E!HR,+w9 v2Ro@v.%Xgb*oWѵ`6WZqcr8H9{Iy7gD&tA]1Z@fBgYsļǾ&NlBlB ԿE]>WVSVS@lkVsL m6q> t0E { ѦڲMc0sMP_H^C,즅zZx^y䩛k̺df-ͪk (mPR*T (U@JP1ueBWt"O+ƶ6XH @xJ5Nt|Q U/Rjxg4F!_}SS .UԴ}'uE%tWё*E%\ᱲ9g.-. ==XXN| m%Qw 6xhU So)Fk"kgbwSI?aOkVke9:o.̌yNfӜdv ˬe?˳+{ )ȑP!r-I PKV1 PKe8lfThumbnails/thumbnail.png sb``p b``2po100z8Ty{Őa/60o͟15Ã>Fhnb'E<]\9%4PKlfPKe8vP% settings.xml͚[s8+>6-LŽ!B6 K5x$CJlB) .ѱ\$@Bbήms9lve 'w ST^"t㙬R^Pb[g\ %۶ kk3`I0{~Yp-Bغ $5p?wv9{³ÊuwĐ+IJE܊UVG߼˩8%*{Nc"'lO[Xr \KI+D>6 Z`쮙)rܯcFX{ֶQ<݈b?ϛo' H0PͣǏ n#(/>S1h.J]-jj&^GFI;)fU(9!8Uz3 aASat 1Lf2ۘXPj~Cj}7\?}Y % $Un\`(߶qC0 (d*o(@N&>1Sݥ.Rʪ|J>p3/p(Yml9Mq_us.A#F "[2;<kx9w3\s_q`y"719`3\'#C$Hxm(}]#E":\@z~d<($NU*SJùy΁(V,# FCB$ жKD_;>E(.2']#38}!=6%$nXcJ328&{գZv/]6zG"uteo}0fxk<J$'UD +-Xʰ׎)4RLdN c]QM%uo {_d @O\|4cbC6X?G'6;} {R秃[ ̑\')^CgNRs6G̯uT@<1= @`,-K)X~E ЕV9p,ʽMuNK`7L^}A4䌬2.4NhpAAdJ_:bzfxOi,vUJ1踩hhݼIO%uɉoMxB&bH`7G ~}0vn-ؐLhwgozĝ9ghƀ׻Ng/ 125cYYM|m{?{\\J|괬G{.Įm~uZzSڛ6E2ezyxߦ¥Z=l>mJqPRm2:=n: '7͛/fMFŠ9\^O?IgueVGЃ:ґ !~ ,ޯPKvP%PKe8/3iMETA-INF/manifest.xmlo {bLd6=aGl_+ pYdnKAC?::UY% ] UlzJnt?[LWyp%*t4lr=yy&,XTf,ym`>J*Y(Nݜm@_AׇvPT )*ou}h8i-5:jqQ3n'Ȋˀpc8xV!ww$wv񬶾(.jQux DzZPK/3iPK8RRmeta.xml OpenOffice.org/2.4$Linux OpenOffice.org_project/680m12$Build-9286OpenOffice::OODoc Presentation TemplateOpenOffice.org 1.0 formatTemplate for the Perl Open Document ConnectorOpenOffice::OODoc Project2008-05-18T20:15:33OpenOffice::OODoc Project2008-05-18T20:15:330PT0M0SPKe8XmimetypePKe8EConfigurations2/statusbar/PKe8'}Configurations2/accelerator/current.xmlPKe8Configurations2/floater/PKe8Configurations2/popupmenu/PKe80Configurations2/progressbar/PKe8jConfigurations2/menubar/PKe8Configurations2/toolbar/PKe8Configurations2/images/Bitmaps/PKe8jY  content.xmlPKe8V1 styles.xmlPKe8lf+Thumbnails/thumbnail.pngPKe8vP% settings.xmlPKe8/3iMETA-INF/manifest.xmlPK8RRmeta.xmlPK OpenOffice-OODoc-2.125/OODoc/templates/template.sxd0000644000175000017500000001642111014071104020102 0ustar jmgjmgPK83иmimetypeapplication/vnd.sun.xml.drawPK8Configurations2/statusbar/PK8'Configurations2/accelerator/current.xmlPK8Configurations2/floater/PK8Configurations2/popupmenu/PK8Configurations2/progressbar/PK8Configurations2/menubar/PK8Configurations2/toolbar/PK8Configurations2/images/Bitmaps/PK8'a$ content.xmlOs ~LS{dLD  ?.U4مiÕ|gLE styles.xml[_o64to$Ic/Na: M7앖($ '{'ّ)ɖ8S ݑ?y'][儦 yӀ$]-?|t.Wo\("4(r'1-s\kγ 4l=sr+/%8wWZa3!C^i!5қfHI6]~,AF7k\VXJc,wl9YhH8j8Z% c_H֯(=ӭ9u7pjAx8&]pXkЄ `霢JB 1 敬$5HX(%Cr1 Jԩo_4)u)n0) ctc[o~w ۳_c)GK-B`^1I ̀]@GE䠂v #3p IhK͊(`5IC,\-p["87(r҅ta)gDTͥMe4 PZ&(UƳ)6r 1pY?*'T$b R$!Es#aT+ ,k{#dWWͯ4IR^}ӬIi۴cX_g4HtaDZ0C OrU캐D4ݫKw;U n4J휣4D,LJ@FLBc*P$ȀP^4e2۪ 8Oe 7{ nıPYD̔ @xF պQ~]Pn_$isidy&]xT|"VԬ$jŚTe|f.X=m T䈣Ru1މDWlimgO@TTEST\$JGZxS:GK9,OVk*S7ea2V[fpssE5CDF'p4QegCb8&8j2ż s +&yxuNqiᲈaݩtEbafyv (}},i nf->Mc|RP-%: =8$9@IDk8U?=WmqGWmqO^4qO_9͎5ugD[U=.o^{j?;G`=݃w(ord,[YΜ18ˡ S49D?J봋[ijh8J1-rXxr+;҅-!,Q}〶\o&w'j t/04 0C`h a04 c#gYL'BuuR+}%]8R}KB{v|϶5~DÝQٓ!5h>ajzuXQ}O"6 jjjj?vkBq_xPK8׃|Thumbnails/thumbnail.png sb``p [8؀{8Ty{i#'ρ |??tCÛw~29K&xrrVoʓԎ_y2cTpTp3 * L ~.PK׃|PK8b" settings.xml͚SH+==`RԢr{6$ 9NLD ݅B>d=xgW"oYqêwѣ|ֲǽ}tt)uU]Rz\6W[\i_=:F1Ol/4\^^@H% {5d=w>PCP&IedSM3uYc.^A3+©U':V6NQji'u7vŏ eRX[HJoT:kR-reggΕ-`Ds~r^/ye54&VgNvB$]륈ն3\h/\E_kY>`G2ΰE-X< ހVb:oF9 } 0}3g]ѫls[‹Cm.ԭGSsE<֩H!'\${Y7ʘ(Xr5Ֆ'8|`U!uUySkBk.!!UQ,Hף FO'(G~%L)C E%ɬ;J2* 얛5.4D'AzқKꐸHP{{C| `g (k"*p) }Ye%ʁQσ(dCoVʘ2~ƴ2=L{"h$9[V= x[}8vL'=>INk  GWy)GgHUf&D_'g[dX{E]gLBnalIArFt# m#2UKƲYVx:h؎+Jogx?)PKb"PK8΂1gMETA-INF/manifest.xmlo {b&Xd#~j2En~`kdEV9f*t)T1}`~:,JTb5~~$,Tf4yi`>J*Y)ÜmL@_^ӅvRT )* OpenOffice.org/2.4$Linux OpenOffice.org_project/680m12$Build-9286OpenOffice::OODoc Drawing TemplateOpenOffice.org 1.0 formatTemplate for the Perl Open Document ConnectorOpenOffice::OODoc Project2008-05-18T20:15:32OpenOffice::OODoc Project2008-05-18T20:15:320PT0M0SPK83иmimetypePK8BConfigurations2/statusbar/PK8'zConfigurations2/accelerator/current.xmlPK8Configurations2/floater/PK8Configurations2/popupmenu/PK8-Configurations2/progressbar/PK8gConfigurations2/menubar/PK8Configurations2/toolbar/PK8Configurations2/images/Bitmaps/PK8'a$ content.xmlPK8")4g> mstyles.xmlPK8׃|> Thumbnails/thumbnail.pngPK8b" settings.xmlPK8΂1g!META-INF/manifest.xmlPK8r.&umeta.xmlPK OpenOffice-OODoc-2.125/OODoc/templates/template.odt0000644000175000017500000001525511320701577020114 0ustar jmgjmgPK%<^2 ''mimetypeapplication/vnd.oasis.opendocument.textPK%<7d content.xmlUT 4CK4CKux V[o0~WDnRAIդIj?_1 >ww/Ӈ-KEUaDEjX|)nh* \V s] fU魳PUrȰ*5*Ey*ctw41[N%[.#;p̮$RkTH%o-jaTg $-%,_kݔt]7nB8kH\JPbLxz,gqJeK,WUY%wf5 ZC|\ޛ*7UePjr >6XKV`ʬTS4e>>$ۦU}1t]G~o\\CE .*2lO`&4-3sj(F^koIqaQ7e q6B}:<;rm s#mrhF<@I%g m//g-j( j_:Nuf/WJ[IGpb\}Ow6juc_0<}HߏPK%@Cm3Hȯ )ʔ,`s`΃Ùo.K*aH_= 6yŽX.YL版r(O4WsK|QL9ɨx.rʝ瞛QW0Қt_aɒE /H+T_|) T,E,'5إ}anGHUfP+//dj8)TfT!o/]C49ڬz#bpM&76 s=Ӥx/]XY߽X1-//LEx|$V2MdIWYӀ/ #xNͷ2dٿ@w&.ɒT  | !fB"/y5~1pt).ILƩަAEh&>[t8;1<YD-j, C [TLr~mᠦT2,8rcH +p~6/Ֆ)_#/O4{Yel;~Nl;36KRD6(-[IYYSkA.!f/s&:= ǣi & "O@$.d $EI BYEgαezعgIRUFy('/>$ H` €1e!)Bi@v,h_&Oe5T0P8#r;0PAEJnCP1ƒXF- h]KaSMrmRWYRX\K/v|sXޑQg-p,;ڷΑm ޢG:V(!lɶYXcxe>_SAJ|gBkyc]X[ C3'@,Σ,H-MFt9sX+'qKz#5Vk(W3JK|fzS^ub\1+>Ur!$b =(%B`_DŽ@mHٯ+xkÔgu5dYC\`S U]UEIe{FX^&vk8f >OƟ"ٷY֧eDB5جo&YBk@&%84qn8IdTlqU5M[b){q䧠I> cNt)GFc%b``IzJ=gta(fpoWJ<>\^tރ_6owW63Fv 2j/_% zWs [x&1s% ʣ۟(RZCZ4Vʑe599Q.bs(8W.rf8`zM<.O 5{=f߹U{,۠S݋yh_nko.n  CJ^Rz(/Bp_Qh6$%{G8=BS:Xlxې8|%!7҈Dq7uw:' u)u6~(m튤fymy{ƒն&.2|Z7PK%<׃|Thumbnails/thumbnail.png sb``p [8؀{8Ty{i#'ρ |??tCÛw~29K&xrrVoʓԎ_y2cTpTp3 * L ~.PK%<'Configurations2/accelerator/current.xmlPK%<Configurations2/progressbar/PK%<Configurations2/floater/PK%<Configurations2/popupmenu/PK%<Configurations2/menubar/PK%<Configurations2/toolbar/PK%<Configurations2/images/Bitmaps/PK%<Configurations2/statusbar/PK%<Ц}$v! settings.xmlZr8}WP~O0L22!27a7vI2ߖmY-t*(QxW`{wޞ]th$HsZ+_fKl"\7%@7Mf[f=Y ._n1qZ].Y~uuUMn(|v(T-" 0)e}*olSn*Kl @*9Jq D@#t{ coƬcz#Pμuu݃qu3|ᗍzsyů7.\0r!L@oL08]R!!L8X|e+w _FJ Ζn%6[ؐ, Q({xQb5jk>pDr~I𳇅ye3SQa~) 1Ӂa9Geȷ#11mI$i!( F xf8RTKAW@@` (zpl1UᒜJo UA|@h,Q$|<䇙)XXJX0g2m`ΜT1#VrJm; =꒟␙}&KZo(\:ެ`⒩W=IT@Nվ [M~ ioQl#SÕ6V.4]DH/7Q7`jc+v@*%Ԉ#RD,g,!)* T9o!pSipMmE^LC9|F b'^?Ic|3-Pn'Xsw&K[~_J4)C)tQ:g}h Hj-UN[(W6ph'JQ,,ih?G`]~hm,Le1IˑxUX)'qy/OV\@i}jmly)uWޞOAW^Y>i$ ['辴ݔKǀ*Y5y&w[1A%RU/P@f )tLMKf;*qv Kw eȒ|328jbejBL t3 HTR܏D>][gnL&LGv"0Ƭz8Lb%JMv95Y쏁NoQ ~S"rɢ8b23xZ⌁]O[/6U(Iԓi5S«ēy((ZeF͝\2-TԵ-U{,@m'-].m຺G;PK%<0㬅>JMETA-INF/manifest.xmlj0 }{m6t:Jj\ڷ_R֟m=!Y -y>XS1Փ()j7sQjtyQ ]Fjv`jVZEz4iU!:޵U**.S]#F*OS X a][ڐ > ݻN)^ HP R],}h zR6lޛlpmGo-L}&NSOɍY%][ʘw0#!^FRW_PK%<^2 ''mimetypePK%<7d Mcontent.xmlUT4CKux PK%JMETA-INF/manifest.xmlPKyOpenOffice-OODoc-2.125/OODoc/templates/template.odp0000644000175000017500000002145211320701413020071 0ustar jmgjmgPK%<3&//mimetypeapplication/vnd.oasis.opendocument.presentationPK%u6XP5t/\瓪BM5`q* ׉%J7 0񸶓EњJ ؆jnj+Yհzv;Q٥`W#M1L*$m>AKNYSc8ڳ' 3mq+zh/O?Z*qĵQ6Th{5tLdsIr*xcpHmí3{L6[%i.m\Bp7ނDh% Cc#GܣjC^\+E!}1)z$vU{~7PK%<䩦 styles.xml]˒F+r;/I\j'&rM8=OAJQ0Y7w̟̗L>%/ $Pꈎ̛p7_'/B?\g=Ei:ᚖ[OgAo2:6 &<4'V B4zbȽh XDm·nc,i 6L֦ucY*|m if[tlv<ţ*n&VM6R9/m"e3hC|`&,=˲*9aЀ0@"\.Ķvt*`6gD8*Y?TTm{¿U^~9ŕ{/,-Tmn*n@;;QW$mFfgŏB7Ί6RChHN! O>XvmlEǪ =-p>a ȶw1 Dr7$b0ý{|;ă73cOLN `<WOX'w gO<}[O4Q*rQ2*d\W?3rI:Hp ";&l Oӝe`Ry C M\ OB7,TLc1/r0"z= gä Pa8}h.oBI.F-DŽj<`Eܻv!hAF^0ͪc'FL( tpmء!.U/{_,_7Z1 ;@zcg^ot!h^Hl"CE[n>n8D .* ރ&' t"@U">-Hjׁeņo|{9RJRj#RR3C:b `Q!cߜqd|A=ږIGZ3d2q+hO*nZ7PY3Siko-N-HKRMW+nIdeTl0S`Y !)TnP\$Ӵeq*S(a?52[^ZK_2Бm=#Tdn&&mNh%.GԔI8[O&WJ")f9p+?~ظPK?m4윁APn6%hSة`9 6M`Z= vZ鏂ݼmQ{mc> r֑{Bo;qfU=L!Km7TʤtkZD^:hPSw Snϲe,.&$}GJ FRiF!id2w^f(IOvYZ?%$Q:!˧,Zzq!څ hSލjX$1IUg*,b**DH4 m n˸ J9R*ጁ el(ϛZ)~Zh8t]+]V`Ϻ44cʯc(xM/]NO{o n\/)p+m= PJV-mĄMH\i{_  ۜ8v6'DŽ'9qaN+RҊ g,dG{L6*3b|5 `En5{a˦J&ׂ!2t0S+DȿW'gɿ'W.wjx4ad9VO3MB\i>2|6)dCo Ge9ƷFqC4}/:$'Z9:p8ʉVN 9NrŽ'ZV/K`&+e$69~ўMh&4C{ xGRG~kv3-n؞]n9"ZaX Q7mBQ:)2nq"ٞNIYTYŊ7q^F<țT' #'=)deؓl ʣ0d6hL~i1_i>o݊S[r k J7Vfqܠ6wC|uP E2F7[r -c8aђ͝?nXEs7mL zKnX6wê aՒn6f!6iЕ` /[Nb;Xi7;M젙$zSO=)+O=y<|LgC))+O=燬u+;'៨'E8)2.8)IN 9NpR"Ž" EJ`4gV8QhhDϱ3 ` ;wo2tOH_$Ǜmhۻ6Iq=M#l Ҝ!wXjjw<.DYҒ}΂$*zb^=d-LI/̠VKNW7ii黫 503Vw{RDȭrT!X#wWdeO⟸c18Q'2"asV%+pb4/}Hi`ځâ_-7?,mb#-M|܃)!_ ˯Ů).!*{`ҧחB N\~/F ==qbZ*Ow<#B Ow <#BNw;#BNw ;#BNW:#FBNw :#BjNw9#BJNw 9#R*Nw8C4N@;;aw|'wCDN 9k%)jI8<- roeLJ:YiItG>ł7>|SNՎ0d2辠&ԈP߫GF#cSh|juoтtP osEWBA$N? h(uregmg7PK%Fhnb'E<]\9%4PK%<'Configurations2/accelerator/current.xmlPK%<Configurations2/progressbar/PK%<Configurations2/floater/PK%<Configurations2/popupmenu/PK%<Configurations2/menubar/PK%<Configurations2/toolbar/PK%<Configurations2/images/Bitmaps/PK%<Configurations2/statusbar/PK%& settings.xml͚s6`<}hKn s\B\{}jdGC__JLpڕn BRMvVJmt(5q|m}i}t)UYRz,\6V/x zۣmQҴJyJeX->Ujz]H-`ɀs6)%=76V ?V/*GK,3l5Oػ"IŘ[ZeTZ[wmTtjU \cR@ֿJaq#-ryߩ4Z1zZz%j?__o*Na" \PGͣ_ ZkS%^r"4`DYbDXUK%X-6@o:=ndȀp5%LBvJW =9aAsa 1\2)XPn4lO_ /w6[en\q'l(7[Vgԥ( [T@e5M>8Ӆvꓥ"V/E,V%u3ˊU>/_P%Gl+Y&;py@LnXg-AW#8Q :Eނt޻B60jM;46LgN SmĨrbh:F6NL>ʱ rdpw ԪٲzL>VZ~9L( E;dѬtQN=TvM[gTQX#a=N%vH zxQc${S}mcg (j/"nj<1ݦ~"~ !g':D'0XcO}LZC;=yj S3#/Ogń:9smuGDME{YK0SD%{]Pvf.`F99*%[3I@$BSY"k(;ٟW- |n+?CŚjc/ RvO");1 ,ECZ>DWyC0fIפLʉjʓu3cόRƔA6acA4gAv w' t"C|2xAfxEF ~ jw4.)N|d=᢬!dQ736ℾKwKϚ7+|_}wʢݦ<²*=sty;cqpSb7ZPK%nN۟hZ̪ 7Ej> 8)wiZфcPdmo;!Kʰg3MySrhrV@oV֨%ɧmp> coA/Ri GS]E!!>U!Aob()Me#؇am1qjo QMyGcs644h? PK%<3&//mimetypePK%& /settings.xmlPK%