1#************************************************************************* 2# 3# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4# 5# Copyright 2000, 2010 Oracle and/or its affiliates. 6# 7# OpenOffice.org - a multi-platform office productivity suite 8# 9# This file is part of OpenOffice.org. 10# 11# OpenOffice.org is free software: you can redistribute it and/or modify 12# it under the terms of the GNU Lesser General Public License version 3 13# only, as published by the Free Software Foundation. 14# 15# OpenOffice.org is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU Lesser General Public License version 3 for more details 19# (a copy is included in the LICENSE file that accompanied this code). 20# 21# You should have received a copy of the GNU Lesser General Public License 22# version 3 along with OpenOffice.org. If not, see 23# <http://www.openoffice.org/license.html> 24# for a copy of the LGPLv3 License. 25# 26#************************************************************************* 27 28#************************************************************************* 29# 30# SourceConfig - Perl extension for parsing general info databases 31# 32# usage: see below 33# 34#************************************************************************* 35 36package SourceConfig; 37 38use strict; 39 40use constant SOURCE_CONFIG_FILE_NAME => 'source_config'; 41use constant SOURCE_CONFIG_VERSION => 3; 42 43use Carp; 44use Cwd; 45use RepositoryHelper; 46use File::Basename; 47use File::Temp qw(tmpnam); 48 49my $debug = 0; 50 51##### profiling ##### 52 53##### ctor ##### 54 55sub new { 56 my $proto = shift; 57 my $class = ref($proto) || $proto; 58 my $source_root = shift; 59 my $self = {}; 60 $self->{USER_SOURCE_ROOT} = undef; 61 $self->{SOURCE_CONFIG_FILE} = undef; 62 if (defined $source_root) { 63 $source_root = Cwd::realpath($source_root); 64 $source_root =~ s/\\|\/$//; 65 if (-f $source_root) { 66 # We have path to source_config 67 if (File::Basename::basename($source_root) eq 'source_config') { 68 # We have path to source_config 69 $self->{SOURCE_CONFIG_FILE} = $source_root; 70 $source_root = File::Basename::dirname($source_root); 71 } else { 72 croak("$source_root is not a source_config file"); 73 }; 74 } else { 75 $self->{USER_SOURCE_ROOT} = $source_root; 76 $source_root .= '/..'; 77 } 78 } else { 79 $source_root = $ENV{SOURCE_ROOT_DIR}; 80 }; 81 $source_root = Cwd::realpath($source_root); 82 $self->{SOURCE_ROOT} = $source_root; 83 $self->{DEBUG} = 0; 84 $self->{VERBOSE} = 0; 85 $self->{REPOSITORIES} = {}; 86 $self->{ACTIVATED_REPOSITORIES} = {}; 87 $self->{MODULE_PATHS} = {}; 88 $self->{MODULE_BUILD_LIST_PATHS} = {}; 89 $self->{ACTIVATED_MODULES} = {}; 90 $self->{MODULE_REPOSITORY} = {}; 91 $self->{REAL_MODULES} = {}; 92 $self->{NEW_MODULES} = []; 93 $self->{REMOVE_MODULES} = {}; 94 $self->{REMOVE_REPOSITORIES} = {}; 95 $self->{NEW_REPOSITORIES} = []; 96 $self->{WARNINGS} = []; 97 $self->{REPORT_MESSAGES} = []; 98 $self->{CONFIG_FILE_CONTENT} = []; 99 if (defined $self->{USER_SOURCE_ROOT}) { 100 ${$self->{REPOSITORIES}}{File::Basename::basename($self->{USER_SOURCE_ROOT})} = $self->{USER_SOURCE_ROOT}; 101 }; 102 $self->{SOURCE_CONFIG_FILE} = get_config_file($self->{SOURCE_ROOT}) if (!defined $self->{SOURCE_CONFIG_FILE}); 103 $self->{SOURCE_CONFIG_DEFAULT} = $self->{SOURCE_ROOT} .'/'.SOURCE_CONFIG_FILE_NAME; 104 if (defined $self->{USER_SOURCE_ROOT}) { 105 ${$self->{REPOSITORIES}}{File::Basename::basename($self->{USER_SOURCE_ROOT})} = $self->{USER_SOURCE_ROOT}; 106 }; 107 read_config_file($self); 108 get_module_paths($self); 109 bless($self, $class); 110 return $self; 111} 112 113##### methods ##### 114 115sub get_version { 116 return SOURCE_CONFIG_VERSION; 117}; 118 119sub get_repositories 120{ 121 my $self = shift; 122 return sort keys %{$self->{REPOSITORIES}}; 123} 124 125sub add_repository 126{ 127 my $self = shift; 128 my $new_rep_path = shift; 129 $new_rep_path = Cwd::realpath($new_rep_path); 130 my $new_rep_name = File::Basename::basename($new_rep_path); 131 if (defined ${$self->{REPOSITORIES}}{$new_rep_name}) { 132 croak("Repository $new_rep_name is already defined!!"); 133 }; 134 ${$self->{REPOSITORIES}}{$new_rep_name} = $new_rep_path; 135 $self -> get_repository_module_paths($new_rep_name); 136} 137 138sub get_config_file_default_path { 139 my $self = shift; 140 return $self->{SOURCE_CONFIG_DEFAULT}; 141} 142 143sub get_config_file_path { 144 my $self = shift; 145 return $self->{SOURCE_CONFIG_FILE}; 146} 147 148sub get_module_repository { 149 my $self = shift; 150 my $module = shift; 151 if (defined ${$self->{MODULE_REPOSITORY}}{$module}) { 152 return ${$self->{MODULE_REPOSITORY}}{$module}; 153 } else { 154 Carp::cluck("No such module $module in active repositories!!\n"); 155 return undef; 156 }; 157} 158 159sub get_module_path { 160 my $self = shift; 161 my $module = shift; 162 if (defined ${$self->{MODULE_PATHS}}{$module}) { 163 return ${$self->{MODULE_PATHS}}{$module}; 164 } else { 165 Carp::cluck("No path for module $module in active repositories!!\n") if ($debug); 166 return undef; 167 }; 168} 169 170sub get_module_build_list { 171 my $self = shift; 172 my $module = shift; 173 if (defined ${$self->{MODULE_BUILD_LIST_PATHS}}{$module}) { 174 return ${$self->{MODULE_BUILD_LIST_PATHS}}{$module}; 175 } else { 176 my @possible_build_lists = ('build.lst', 'build.xlist'); # build lists names 177 foreach (@possible_build_lists) { 178 my $possible_path = ${$self->{MODULE_PATHS}}{$module} . "/prj/$_"; 179 if (-e $possible_path) { 180 ${$self->{MODULE_BUILD_LIST_PATHS}}{$module} = $possible_path; 181 return $possible_path; 182 }; 183 }; 184 Carp::cluck("No build list in module $module found!!\n") if ($self->{DEBUG}); 185 return undef; 186 }; 187} 188 189sub get_all_modules 190{ 191 my $self = shift; 192 my $module = shift; 193 return sort keys %{$self->{MODULE_PATHS}}; 194}; 195 196sub get_active_modules 197{ 198 my $self = shift; 199 if (scalar keys %{$self->{ACTIVATED_MODULES}}) { 200 return sort keys %{$self->{ACTIVATED_MODULES}}; 201 } 202 return sort keys %{$self->{REAL_MODULES}}; 203} 204 205sub is_active 206{ 207 my $self = shift; 208 my $module = shift; 209 if (scalar keys %{$self->{ACTIVATED_MODULES}}) { 210 return exists ($self->{ACTIVATED_MODULES}{$module}); 211 } 212 return exists ($self->{REAL_MODULES}{$module}); 213} 214 215##### private methods ##### 216 217sub get_repository_module_paths { 218 my $self = shift; 219 my $repository = shift; 220 my $repository_path = ${$self->{REPOSITORIES}}{$repository}; 221 if (opendir DIRHANDLE, $repository_path) { 222 foreach my $module (readdir(DIRHANDLE)) { 223 next if (($module =~ /^\.+/) || (!-d "$repository_path/$module")); 224 my $module_entry = $module; 225 if (($module !~ s/\.lnk$//) && ($module !~ s/\.link$//)) { 226 $self->{REAL_MODULES}{$module}++; 227 } 228 my $possible_path = "$repository_path/$module_entry"; 229 if (-d $possible_path) { 230 if (defined ${$self->{MODULE_PATHS}}{$module}) { 231 close DIRHANDLE; 232 croak("Ambiguous paths for module $module: $possible_path and " . ${$self->{MODULE_PATHS}}{$module}); 233 }; 234 ${$self->{MODULE_PATHS}}{$module} = $possible_path; 235 ${$self->{MODULE_REPOSITORY}}{$module} = $repository; 236 } 237 }; 238 close DIRHANDLE; 239 } else { 240 croak("Cannot read $repository_path repository content"); 241 }; 242}; 243 244sub get_module_paths { 245 my $self = shift; 246 foreach my $repository (keys %{$self->{REPOSITORIES}}) { 247 get_repository_module_paths($self, $repository); 248 }; 249 my @false_actives = (); 250 foreach (keys %{$self->{ACTIVATED_MODULES}}) { 251 push(@false_actives, $_) if (!defined ${$self->{MODULE_PATHS}}{$_}); 252 }; 253 croak("Error!! Activated module(s): @false_actives\nnot found in the active repositories!! Please check your " . $self->{SOURCE_CONFIG_FILE} . "\n") if (scalar @false_actives); 254 croak("No modules found!") if (!scalar keys %{$self->{MODULE_PATHS}}); 255}; 256 257sub get_config_file { 258 my $source_root = shift; 259 my $possible_path = $source_root . '/' . SOURCE_CONFIG_FILE_NAME; 260 return $possible_path if (-f $possible_path); 261 return ''; 262}; 263 264# 265# Fallback - fallback repository is based on RepositoryHelper educated guess 266# 267sub get_fallback_repository { 268 my $self = shift; 269 my $repository_root = RepositoryHelper->new()->get_repository_root(); 270 ${$self->{REPOSITORIES}}{File::Basename::basename($repository_root)} = $repository_root; 271}; 272 273sub read_config_file { 274 my $self = shift; 275 if (!$self->{SOURCE_CONFIG_FILE}) { 276 if (!defined $self->{USER_SOURCE_ROOT}) { 277 get_fallback_repository($self); 278 }; 279 return; 280 }; 281 my $repository_section = 0; 282 my $module_section = 0; 283 my $line = 0; 284 my @file_content = (); 285 286 if (open(SOURCE_CONFIG_FILE, $self->{SOURCE_CONFIG_FILE})) { 287 foreach (<SOURCE_CONFIG_FILE>) { 288 push (@{$self->{CONFIG_FILE_CONTENT}}, $_); 289 $line++; 290 chomp; 291 next if (!/^\S+/); 292 next if (/^\s*#+/); 293 s/\r\n//; 294 if (/^\[repositories\]\s*(\s+#)*/) { 295 $module_section = 0; 296 $repository_section = 1; 297 next; 298 }; 299 if (/^\[modules\]\s*(\s+#)*/) { 300 $module_section = 1; 301 $repository_section = 0; 302 next; 303 }; 304 next if (!$repository_section && !$module_section); 305 if (/\s*(\S+)=active\s*(\s+#)*/) { 306 if ($repository_section) { 307 my $repository_source_path = $self->{SOURCE_ROOT} . "/$1"; 308 if (defined $ENV{UPDMINOREXT}) { 309 $repository_source_path .= $ENV{UPDMINOREXT}; 310 if (defined ${$self->{REPOSITORIES}}{$1.$ENV{UPDMINOREXT}}) { 311 delete ${$self->{REPOSITORIES}}{$1.$ENV{UPDMINOREXT}}; 312 }; 313 }; 314 ${$self->{REPOSITORIES}}{$1} = $repository_source_path; 315 ${$self->{ACTIVATED_REPOSITORIES}}{$1}++; 316 next; 317 } 318 if ($module_section) { 319 ${$self->{ACTIVATED_MODULES}}{$1}++; 320 next; 321 }; 322 }; 323 croak("Line $line in " . $self->{SOURCE_CONFIG_FILE} . ' violates format. Please make your checks!'); 324 }; 325 close SOURCE_CONFIG_FILE; 326 if (!scalar keys %{$self->{REPOSITORIES}}) { 327 get_fallback_repository($self); 328 }; 329 } else { 330 croak('Cannot open ' . $self->{SOURCE_CONFIG_FILE} . ' for reading'); 331 }; 332}; 333 334sub remove_all_activated_repositories { 335 my $self = shift; 336 $self->remove_activated_repositories([keys %{$self->{ACTIVATED_REPOSITORIES}}]); 337}; 338 339sub remove_activated_repositories { 340 my $self = shift; 341 my $new_repositories_ref = shift; 342 push(@{$self->{WARNINGS}}, "\nWARNING: Empty repository list passed for removing from source_config\n") if (!scalar @$new_repositories_ref); 343 $self->{VERBOSE} = shift; 344 $self->{REMOVE_REPOSITORIES} = {}; 345 foreach (@$new_repositories_ref) { 346 if (!defined ${$self->{ACTIVATED_REPOSITORIES}}{$_}) { 347 push (@{$self->{WARNINGS}}, "\nWARNING: repository $_ is not activated in ". $self->get_config_file_default_path()."\n"); 348 } else { 349 ${$self->{REMOVE_REPOSITORIES}}{$_}++; 350 delete ${$self->{ACTIVATED_REPOSITORIES}}{$_}; 351 }; 352 }; 353 generate_config_file($self); 354}; 355 356sub remove_all_activated_modules { 357 my $self = shift; 358 $self->remove_activated_modules([keys %{$self->{ACTIVATED_MODULES}}]); 359}; 360 361sub remove_activated_modules { 362 my $self = shift; 363 my $new_modules_ref = shift; 364 push(@{$self->{WARNINGS}}, "\nWARNING: Empty module list passed for removing from source_config\n") if (!scalar @$new_modules_ref); 365 $self->{VERBOSE} = shift; 366 $self->{REMOVE_MODULES} = {}; 367 foreach (@$new_modules_ref) { 368 if (!defined ${$self->{ACTIVATED_MODULES}}{$_}) { 369 push (@{$self->{WARNINGS}}, "\nWARNING: module $_ is not activated in ". $self->get_config_file_default_path()."\n"); 370 } else { 371 ${$self->{REMOVE_MODULES}}{$_}++; 372 delete ${$self->{ACTIVATED_MODULES}}{$_}; 373 }; 374 }; 375 generate_config_file($self); 376}; 377 378sub add_active_repositories { 379 my $self = shift; 380 $self->{NEW_REPOSITORIES} = shift; 381 croak('Empty repository list passed for addition to source_config') if (!scalar @{$self->{NEW_REPOSITORIES}}); 382 $self->{VERBOSE} = shift; 383 foreach (@{$self->{NEW_REPOSITORIES}}) { 384 $self->add_repository($_); 385 }; 386 generate_config_file($self); 387}; 388 389sub add_active_modules { 390 my $self = shift; 391 my $module_list_ref = shift; 392 my $ignored_modules_string = ''; 393 my @real_modules = (); 394 foreach my $module (sort @$module_list_ref) { 395 if ($self->get_module_path($module)) { 396 push(@real_modules, $module); 397 } else { 398 $ignored_modules_string .= " $module"; 399 }; 400 }; 401 push (@{$self->{WARNINGS}}, "\nWARNING: following modules are not found in active repositories, and have not been added to the " . $self->get_config_file_default_path() . ":$ignored_modules_string\n") if ($ignored_modules_string); 402 $self->{NEW_MODULES} = \@real_modules; 403 croak('Empty module list passed for addition to source_config') if (!scalar @{$self->{NEW_MODULES}}); 404 $self->{VERBOSE} = shift; 405 generate_config_file($self); 406}; 407 408sub add_content { 409 my $self = shift; 410 my $content = shift; 411 my $entries_to_add = shift; 412 return if (!scalar @$entries_to_add); 413 my $message; 414 my $message_part1; 415 my $warning_message; 416 my $activated_entries; 417 418 if ($entries_to_add == $self->{NEW_MODULES}) { 419 $self->{NEW_MODULES} = []; 420 $message_part1 = "Module(s):\n"; 421 $activated_entries = $self->{ACTIVATED_MODULES}; 422 } elsif ($entries_to_add == $self->{NEW_REPOSITORIES}) { 423 $self->{NEW_REPOSITORIES} = []; 424 $message_part1 = "Repositories:\n"; 425 $activated_entries = $self->{ACTIVATED_REPOSITORIES}; 426 }; 427 foreach my $entry (@$entries_to_add) { 428 if (defined $$activated_entries{$entry}) { 429 $warning_message .= "$entry " 430 } else { 431 push(@$content, "$entry=active\n"); 432 ${$activated_entries}{$entry}++; 433 $message .= "$entry " 434 }; 435 }; 436 437 push(@{$self->{REPORT_MESSAGES}}, "\n$message_part1 $message\nhave been added to the ". $self->get_config_file_default_path()."\n") if ($message); 438 push (@{$self->{WARNINGS}}, "\nWARNING: $message_part1 $warning_message\nare already added to the ". $self->get_config_file_default_path()."\n") if ($warning_message); 439}; 440 441sub generate_config_file { 442 my $self = shift; 443 my @config_content_new = (); 444 my ($module_section, $repository_section); 445 my %removed_modules = (); 446 my %removed_repositories = (); 447 foreach (@{$self->{CONFIG_FILE_CONTENT}}) { 448 if (/^\[repositories\]\s*(\s+#)*/) { 449 if ($module_section) { 450 $self->add_content(\@config_content_new, $self->{NEW_MODULES}); 451 }; 452 $module_section = 0; 453 $repository_section = 1; 454 }; 455 if (/^\[modules\]\s*(\s+#)*/) { 456 if ($repository_section) { 457 $self->add_content(\@config_content_new, $self->{NEW_REPOSITORIES}); 458 }; 459 $module_section = 1; 460 $repository_section = 0; 461 }; 462 if ($module_section && /\s*(\S+)=active\s*(\s+#)*/) { 463 if (defined ${$self->{REMOVE_MODULES}}{$1}) { 464 $removed_modules{$1}++; 465 next; 466 }; 467 } 468 if ($repository_section && /\s*(\S+)=active\s*(\s+#)*/) { 469 if (defined ${$self->{REMOVE_REPOSITORIES}}{$1}) { 470 $removed_repositories{$1}++; 471 next; 472 }; 473 } 474 push(@config_content_new, $_); 475 }; 476 if (scalar @{$self->{NEW_MODULES}}) { 477 push(@config_content_new, "[modules]\n") if (!$module_section); 478 $self->add_content(\@config_content_new, $self->{NEW_MODULES}); 479 }; 480 if (scalar @{$self->{NEW_REPOSITORIES}}) { 481 push(@config_content_new, "[repositories]\n") if (!$repository_section); 482 $self->add_content(\@config_content_new, $self->{NEW_REPOSITORIES}); 483 }; 484 if (scalar keys %removed_modules) { 485 my @deleted_modules = keys %removed_modules; 486 push(@{$self->{REPORT_MESSAGES}}, "\nModules: @deleted_modules\nhave been removed from the ". $self->get_config_file_default_path()."\n"); 487 488 }; 489 if (scalar keys %removed_repositories) { 490 my @deleted_repositories = keys %removed_repositories; 491 push(@{$self->{REPORT_MESSAGES}}, "\nRepositories: @deleted_repositories\nhave been removed from the ". $self->get_config_file_default_path()."\n"); 492 493 }; 494 495 # Writing file, printing warnings and reports 496 497 #check if we need to write a new file 498 my $write_needed = 0; 499 if ((scalar @{$self->{CONFIG_FILE_CONTENT}}) != (scalar @config_content_new)) { 500 $write_needed++; 501 } else { 502 foreach my $i (0 .. $#{$self->{CONFIG_FILE_CONTENT}}) { 503 if (${$self->{CONFIG_FILE_CONTENT}}[$i] ne $config_content_new[$i]) { 504 $write_needed++; 505 last; 506 }; 507 }; 508 }; 509 if ($write_needed) { 510 my $temp_config_file = File::Temp::tmpnam($ENV{TMP}); 511 die("Cannot open $temp_config_file") if (!open(NEW_CONFIG, ">$temp_config_file")); 512 print NEW_CONFIG $_ foreach (@config_content_new); 513 close NEW_CONFIG; 514 rename($temp_config_file, $self->get_config_file_default_path()) or system("mv", $temp_config_file, $self->get_config_file_default_path()); 515 if (-e $temp_config_file) { 516 system("rm -rf $temp_config_file") if (!unlink $temp_config_file); 517 }; 518 $self->{CONFIG_FILE_CONTENT} = \@config_content_new; 519 }; 520 if ($self->{VERBOSE}) { 521 print $_ foreach (@{$self->{WARNINGS}}); 522 $self->{VERBOSE} = 0; 523 }; 524 $self->{WARNINGS} = []; 525 print $_ foreach (@{$self->{REPORT_MESSAGES}}); 526 $self->{REPORT_MESSAGES} = []; 527}; 528 529##### finish ##### 530 5311; # needed by use or require 532 533__END__ 534 535=head1 NAME 536 537SourceConfig - Perl extension for parsing general info databases 538 539=head1 SYNOPSIS 540 541 # example that will read source_config file and return the active repositories 542 543 use SourceConfig; 544 545 # Create a new instance of the parser: 546 $a = SourceConfig->new(); 547 548 # Get repositories for the actual workspace: 549 $a->get_repositories(); 550 551 # Add a repository new_repository for the actual workspace (via full path): 552 $a->add_repository(/DEV300/new_repository); 553 554=head1 DESCRIPTION 555 556SourceConfig is a perl extension to load and parse General Info Databses. 557It uses a simple object oriented interface to retrieve the information stored 558in the database. 559 560Methods: 561 562SourceConfig::new() 563 564Creates a new instance of SourceConfig. Can be initialized by: path to the default repository, path to the source_config, default - empty, the source_config will be taken from the environment 565 566 567SourceConfig::get_version() 568 569Returns version number of the module. Can't fail. 570 571 572SourceConfig::get_repositories() 573 574Returns sorted list of active repositories for the actual workspace 575 576 577SourceConfig::add_repository(REPOSITORY_PATH) 578 579Adds a repository to the list of active repositories 580 581 582SourceConfig::get_active_modules() 583 584Returns a sorted list of active modules 585 586SourceConfig::get_all_modules() 587 588Returns sorted list of all modules in active repositories. 589 590SourceConfig::get_module_path($module) 591 592Returns absolute module path 593 594SourceConfig::get_module_build_list($module) 595 596Returns absolute module build list path 597 598SourceConfig::get_module_repository($module) 599 600Returns the module's repository 601 602SourceConfig::get_config_file_path() 603 604Returns absolute module to the source configuration file 605 606SourceConfig::get_config_file_default_path() 607 608Returns default path for source configuration file 609 610SourceConfig::is_active() 611 612Returns 1 (TRUE) if a module is active 613Returns 0 (FALSE) if a module is not active 614 615SourceConfig::add_active_modules($module_array_ref) 616 617Adds modules from the @$module_array_ref as active to the source_config file 618 619SourceConfig::add_active_repositories($repository_array_ref) 620 621Adds repositories from the @$repository_array_ref as active to the source_config file 622 623SourceConfig::remove_activated_modules($module_array_ref) 624 625Removes modules from the @$module_array_ref from the source_config file 626 627SourceConfig::remove_all_activated_modules() 628 629Removes all activated modules from the source_config file 630 631SourceConfig::remove_activated_repositories($repository_array_ref) 632 633Removes repositories from the @$repository_array_ref from the source_config file 634 635SourceConfig::remove_all_activated_repositories() 636 637Removes all activated repositories from the source_config file 638 639 640=head2 EXPORT 641 642SourceConfig::new() 643SourceConfig::get_version() 644SourceConfig::get_repositories() 645SourceConfig::add_repository() 646SourceConfig::get_active_modules() 647SourceConfig::get_all_modules() 648SourceConfig::get_module_path($module) 649SourceConfig::get_module_build_list($module) 650SourceConfig::get_module_repository($module) 651SourceConfig::get_config_file_path() 652SourceConfig::get_config_file_default_path() 653SourceConfig::is_active($module) 654SourceConfig::add_active_modules($module_array_ref) 655SourceConfig::add_active_repositories($repository_array_ref) 656SourceConfig::remove_activated_modules($module_array_ref) 657SourceConfig::remove_all_activated_modules() 658SourceConfig::remove_activated_repositories($repository_array_ref) 659SourceConfig::remove_all_activated_repositories() 660 661=head1 AUTHOR 662 663Vladimir Glazunov, vg@openoffice.org 664 665=head1 SEE ALSO 666 667perl(1). 668 669=cut 670