1: 2eval 'exec perl -wS $0 ${1+"$@"}' 3 if 0; 4#************************************************************** 5# 6# Licensed to the Apache Software Foundation (ASF) under one 7# or more contributor license agreements. See the NOTICE file 8# distributed with this work for additional information 9# regarding copyright ownership. The ASF licenses this file 10# to you under the Apache License, Version 2.0 (the 11# "License"); you may not use this file except in compliance 12# with the License. You may obtain a copy of the License at 13# 14# http://www.apache.org/licenses/LICENSE-2.0 15# 16# Unless required by applicable law or agreed to in writing, 17# software distributed under the License is distributed on an 18# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19# KIND, either express or implied. See the License for the 20# specific language governing permissions and limitations 21# under the License. 22# 23#************************************************************** 24 25 26 27#here the definition for d would be written into dependencies. The reason is that when the event handler 28#for the element is called, we can only find out the namespace but not the prefix. So we cannot 29#distinguish if the namespace is used because the element was prefixed or because it uses the default 30#namespace. 31use warnings; 32use strict; 33 34use XML::Parser; 35use Getopt::Long; 36use Carp; 37 38sub getUpdateInfoFileName($); 39sub writeUpdateInformationData($); 40sub findAttribute($$); 41sub getNotDefPrefs($$$); 42sub collectPrefixes($$$$); 43sub determineNsDefinitions($$$); 44sub determineNsDefinitionForItem($$$); 45 46my $inDescription = 0; 47my $inDependencies = 0; 48my $inIdentifier = 0; 49my $inVersion = 0; 50my $descNS = "http://openoffice.org/extensions/description/2006"; 51my $indent; 52my $identifier; 53my $version; 54 55#contains prefixes and the corresponding namespaces which are used in the <dependencies> 56#element and all children of the description.xml 57my @usedNsInDependencies; 58 59#Maps prefix to namespaces which are valid in <dependencies>. That is, they are 60#either defined in <dependencies> or in the hierarchy above <dependencies> 61my %validPrefsInDep; 62#Contains the prefixes which are defined in <dependencies> 63my @newPrefsInDep; 64#Contains the prefixes/namespaces which need to be defined in <dependencies> but which are currently 65#not. For example a prefix is defined in the parent and is used in a child of <dependencies> 66my %notDefInDep; 67 68#prefix used in start and end element 69my $prefix; 70 71#The default namespace valid in <dependencies> 72my $defNsInDep; 73#The prefix which we use for the default namespace used in <dependencies> 74my $generatedPrefix; 75 76my $helptext = 77"make_ext_update_info.pl produces an update information file for an extension. ". 78"It will use a dummy URL as URL for the extension update unless a URL has been ". 79"provided with the --update_url option. The name of the update ". 80"information file, which must be provided with the --out switch, should be formed ". 81"according to this scheme:\n\n". 82"extension_identifier.update.xml\n\n". 83"extension_identifier should correspond to the extension identifier. In some cases ". 84"this may not be possible because the identifier may contain characters which are not ". 85"allowd in file names.\n\n". 86"usage:\n". 87"perl make_ext_update_info.pl [--help][--update_url url] --out update_information_file description.xml\n\n". 88"Options:\n". 89"--help - prints the help message and exits\n". 90"--out file - the update information file to be written including the path\n". 91"--update-url url - inserts the url under the <update-download> element. It may be necessary to enclose the urls in quotes in case they contain characters such as \"?\". ". 92"It can be used multiple times\n\n"; 93 94#handling of arguments 95my $help = 0; 96my $out; 97my @update_urls; 98if (!GetOptions('help|?' => \$help, 99 'out=s' => \$out, 100 'update-url=s'=> \@update_urls)) 101{ 102 print $helptext; 103 exit -1; 104} 105my $cArgs = scalar @ARGV; 106die "You need to provide a description.xml\n\n$helptext" if $cArgs ==0; 107die "You need to provide the name of the update information file ". 108 "with the --out switch.\n" unless ($out); 109die "Too many arguments.\n\n$helptext" if $cArgs > 1; 110print $helptext if $help; 111 112 113#open the update information file for writing 114my $FH; 115open $FH, "> $out" or die $!; 116 117#write the xml header and root element 118print $FH '<?xml version="1.0" encoding="UTF-8"?>', "\n"; 119print $FH '<description xmlns="http://openoffice.org/extensions/update/2006"', "\n"; 120print $FH ' xmlns:xlink="http://www.w3.org/1999/xlink">', "\n"; 121 122#obtain from description.xml the data for the update information 123writeUpdateInformationData($ARGV[0]); 124#We will die if there is no <version> or <identifier> in the description.xml 125die "Error: The description.xml does not contain a <identifier> element.\n" unless $identifier; 126die "Error: The description.xml does not contain a <version> element.\n" unless $version; 127 128#write the update-download element and the children. 129#the indentation of <update-download> corresponds to that of <version> 130print $FH ' 'x$indent, '<update-download>', "\n"; 131#check if update-urls have been provided through --update-url option 132if (scalar @update_urls) 133{ 134 my $urlIndent = $indent > 8 ? 8 : 2 * $indent; 135 #use provided urls 136 for (@update_urls) 137 { 138 print $FH ' 'x$urlIndent, '<src xlink:href="'.$_.'" />', "\n"; 139 } 140} 141else 142{ 143 #use dummy update url 144 print $FH ' 'x8, '<src xlink:href="http://extensions.openoffice.org/testarea/dummy.oxt" />', "\n"; 145} 146print $FH ' 'x$indent, '</update-download>', "\n"; 147 148print $FH '</description>', "\n"; 149close $FH; 150 151exit 0; 152 153 154 155sub start_handler 156{ 157 my $parser = shift; 158 my $name = shift; 159 160 if ($name eq "description" 161 && $descNS eq $parser->namespace($name)) 162 { 163 $inDescription = 1; 164 } 165 elsif ($inDescription 166 && $name eq "version" 167 && $descNS eq $parser->namespace($name)) 168 { 169 $inVersion = 1; 170 $version = 1; 171 $indent = $parser->current_column(); 172 print $FH " "x$indent, $parser->original_string(); 173 } 174 elsif ($inDescription 175 && $name eq "identifier" 176 && $descNS eq $parser->namespace($name)) 177 { 178 $inIdentifier = 1; 179 $identifier = 1; 180 print $FH " "x$parser->current_column(), $parser->original_string(); 181 } 182 elsif ($inDescription 183 && $name eq "dependencies" 184 && $descNS eq $parser->namespace($name)) 185 { 186 $inDependencies = 1; 187 my $dep = $parser->original_string(); 188 #add the additional namespace definitions, which we have discovered during the first 189 #parsing 190 #cut of the closing > or /> from the start element, so we can append the namespace definitions 191 $dep =~ /(\s*<.*) ((\s*\/>)|(\s*>))/x; 192 my $dep1 = $1; 193 $dep1.= " xmlns:".$_.'="'.$notDefInDep{$_}.'"' for (keys %notDefInDep); 194 $dep1.= $2; 195 print $FH " "x$parser->current_column(), $dep1; 196 } 197 elsif ($inDependencies) 198 { 199 #$prefix is global because we need to use it in the end element as well. 200 $prefix = ""; 201 my $fullString; 202 my $orig = $parser->original_string(); 203 #Split up the string so we can insert the prefix for the element. 204 # <OpenOffice.org-minimal-version> 205 # <d:OpenOffice.org-minimal-version> 206 $orig=~/(\s*<)(.*?)\s/x; 207 #in $2 is the element name, look for the prefix 208 if ($2 !~/(.*?):/ && $parser->namespace($name)) { 209 #no prefix, that is element uses default namespace. 210 #Now check if the default namespace in <dependencies> is the same as the one in this 211 #element. If not, then the default ns was defined "after" <dependencies>. Because all 212 #children of <dependencies> are copied into the update information, so will this default 213 #namespace definition. Hence this element will have the same default namespace in the 214 #update information. 215 my $defNsDep = $validPrefsInDep{"#default"}; 216 #we must have #default, see the if statement above 217 my $defNsCur = $parser->expand_ns_prefix("#default"); 218 219 if ($defNsDep eq $defNsCur) { 220 #Determine if there is in <dependency> a prefix defined (only valid there and need not 221 #directly defined in this element). If there is no prefix defined then we will 222 #add a new definition to <dependencies>. 223 for (keys %validPrefsInDep) { 224 if (($validPrefsInDep{$_} eq $defNsDep) && $_ ne "#default") { 225 $prefix = $_; last; 226 } 227 } 228 if (! $prefix) { 229 #If there was no prefix, we will add new prefix definition to <dependency> 230 #Which prefix this is has been determined during the first parsing. 231 for (keys %notDefInDep) { 232 if (($notDefInDep{$_} eq $defNsCur) && $_ ne "#default") { 233 $prefix = $_; last; 234 } 235 } 236 } 237 #die if we have no prefix 238 confess "No prefix defined for default namespace " unless $prefix; 239 #get the full part after < 240 $orig=~/(\s*<)(.*)/x; 241 $fullString= $1.$prefix.":".$2; 242 } 243 244 } 245 $fullString = $orig unless $fullString; 246 247 # We record anything within <dependencies> </dependencies>. 248 print $FH $fullString; 249 } 250} 251 252sub end_handler 253{ 254 my $parser = shift; 255 my $name = shift; 256 257 if ($name eq "description" 258 && $descNS eq $parser->namespace($name)) 259 { 260 $inDescription = 0; 261 } 262 elsif ($inDescription 263 && $name eq "version" 264 && $descNS eq $parser->namespace($name)) 265 { 266 $inVersion = 0; 267 print $FH $parser->original_string(), "\n"; 268 } 269 elsif ($inDescription 270 && $name eq "identifier" 271 && $descNS eq $parser->namespace($name)) 272 { 273 $inIdentifier = 0; 274 print $FH $parser->original_string(), "\n"; 275 } 276 elsif($inDescription 277 && $name eq "dependencies" 278 && $descNS eq $parser->namespace($name)) 279 { 280 $inDependencies = 0; 281 print $FH $parser->original_string(), "\n"; 282 } 283 elsif ($inDependencies) 284 { 285 my $orig = $parser->original_string(); 286 #$orig is empty if we have tags like this: <name /> 287 if ($orig && $prefix) { 288 $orig=~/(\s*<\/)(.*)/x; 289 $orig= $1.$prefix.":".$2; 290 } 291 print $FH $orig; 292 } 293} 294 295#We write the complete content between start and end tags of 296# <identifier>, <version>, <dependencies> 297sub default_handler 298{ 299 my $parser = shift; 300 my $name = shift; 301 if ($inIdentifier || $inVersion) { 302 print $FH $parser->original_string(); 303 } elsif ($inDependencies) { 304 print $FH $parser->original_string(); 305 } 306 307} # End of default_handler 308 309#sax handler used for the first parsing to recognize the used prefixes in <dependencies > and its 310#children and to find out if we need to define a new prefix for the current default namespace. 311sub start_handler_infos 312{ 313 my $parser = shift; 314 my $name = shift; 315 if ($name eq "description" 316 && $descNS eq $parser->namespace($name)) { 317 $inDescription = 1; 318 } 319 elsif ($inDescription 320 && $name eq "dependencies" 321 && $descNS eq $parser->namespace($name)) { 322 $inDependencies = 1; 323 #build the map of prefix/namespace which are valid in <dependencies> 324 my @cur = $parser->current_ns_prefixes(); 325 for (@cur) { 326 $validPrefsInDep{$_} = $parser->expand_ns_prefix($_); 327 } 328 #remember the prefixes defined in <dependencies> 329 @newPrefsInDep = $parser->new_ns_prefixes(); 330 331 collectPrefixes($parser, $name, \@_, \@usedNsInDependencies); 332 return if $generatedPrefix; 333 334 #determine if need to create a new prefix for the current element if it uses a default ns. 335 #Split up the string so we can see if there is a prefix used 336 # <OpenOffice.org-minimal-version> 337 # <d:OpenOffice.org-minimal-version> 338 my $orig = $parser->original_string(); 339 $orig=~/(\s*<)(.*?)\s/x; 340 #in $2 is the element name, look for the prefix 341 if ($2 !~/(.*?):/ && $parser->namespace($name)) { 342 #no prefix, that is element uses default namespace. 343 #Now check if the default namespace in <dependencies> is the same as the one in this 344 #element. If not, then the default ns was defined "after" <dependencies>. Because all 345 #children of <dependencies> are copied into the update information, so will this default 346 #namespace definition. Hence this element will have the same default namespace in the 347 #update information. 348 my $defNsDep = $validPrefsInDep{"#default"}; 349 #we must have #default, see the if statement above 350 my $defNsCur = $parser->expand_ns_prefix("#default"); 351 352 if ($defNsDep eq $defNsCur) { 353 #Determine if there is in <dependency> a prefix defined (only valid there and need not 354 #directly defined in this element). If there is no prefix defined then we will 355 #add a new definition to <dependencies>. 356 for (keys %validPrefsInDep) { 357 if (($validPrefsInDep{$_} eq $defNsDep) && $_ ne "#default") { 358 $prefix = $_; last; 359 } 360 } 361 362 if (! $prefix) { 363 364 #define a new prefix 365 #actually there can be only one prefix, which is the case when the element 366 #uses the same default namespace as <dependencies> otherwise, the default 367 #namespace was redefined by the children of <dependencies>. These are completely 368 #copied and still valid in the update information file 369 $generatedPrefix = "a"; 370 $defNsInDep = $defNsDep; 371 } 372 } 373 } 374 375 } 376 elsif ($inDependencies) { 377 determineNsDefinitions($parser, $name, \@_); 378 collectPrefixes($parser, $name, \@_, \@usedNsInDependencies); 379 } 380} 381#sax handler used for the first parsing to recognize the used prefixes in <dependencies > and its 382#children 383sub end_handler_infos 384{ 385 my $parser = shift; 386 my $name = shift; 387 388 if ($name eq "description" 389 && $descNS eq $parser->namespace($name)) { 390 $inDescription = 0; 391 } 392 elsif($inDescription 393 && $name eq "dependencies" 394 && $descNS eq $parser->namespace($name)) { 395 $inDependencies = 0; 396 } 397} 398 399sub writeUpdateInformationData($) 400{ 401 my $desc = shift; 402 { 403 #parse description xml to collect information about all used 404 #prefixes and names within <dependencies> 405 406 my $parser = new XML::Parser(ErrorContext => 2, 407 Namespaces => 1); 408 $parser->setHandlers(Start => \&start_handler_infos, 409 End => \&end_handler_infos); 410 411 $parser->parsefile($desc); 412 413 414 } 415 #remove duplicates in the array containing the prefixes 416 if ($generatedPrefix) { 417 my %hashtmp; 418 @usedNsInDependencies = grep(!$hashtmp{$_}++, @usedNsInDependencies); 419 420 #check that the prefix for the default namespace in <dependencies> does not clash 421 #with any other prefixes 422 my $clash; 423 do { 424 $clash = 0; 425 for (@usedNsInDependencies) { 426 if ($_ eq $generatedPrefix) { 427 $generatedPrefix++; 428 $clash = 1; last; 429 } 430 } 431 } while ($clash); 432 $notDefInDep{$generatedPrefix} = $defNsInDep; 433 } 434 #if $notDefInDep contains the prefix #default then we need to add the generated prefix as well 435 436 #add the special prefix for the default namespace into the map of prefixes that will be 437 #added to the <dependencies> element in the update information file 438 439 440 ($inDependencies, $inDescription) = (0,0); 441 { 442 my $parser = new XML::Parser(ErrorContext => 2, 443 Namespaces => 1); 444 $parser->setHandlers( 445 Start => \&start_handler, 446 End => \&end_handler, 447 Default => \&default_handler); 448 $parser->parsefile($desc); 449 } 450} 451 452# param 1: name of the attribute we look for 453# param 2: array of name value pairs, the first subscript is the attribute and the second 454# is the value. 455sub findAttribute($$) 456{ 457 my ($name, $args_r) = @_; 458 my @args = @{$args_r}; 459 my $value; 460 while (my $attr = shift(@args)) 461 { 462 if ($attr eq $name) { 463 $value = shift(@args); 464 die "href attribut has no valid URL" unless $value; 465 last; 466 } else { # shift away the following value for the attribute 467 shift(@args); 468 } 469 } 470 return $value; 471} 472 473#collect the prefixes used in an xml element 474#param 1: parser, 475#param 2: element name, 476#param 3: array of name and values of attributes 477#param 4: out parameter, the array containing the prefixes 478sub collectPrefixes($$$$) 479{ 480 my $parser = shift; 481 my $name = shift; 482 my $attr_r = shift; 483 my $out_r = shift; 484 #get the prefixes which are currently valid 485 my @cur = $parser->current_ns_prefixes(); 486 my %map_ns; 487 #get the namespaces for the prefixes 488 for (@cur) { 489 if ($_ eq '#default') { 490 next; 491 } 492 my $ns = $parser->expand_ns_prefix($_); 493 $map_ns{$ns} = $_; 494 } 495 #investigate ns of element 496 my $pref = $map_ns{$parser->namespace($name)}; 497 push(@{$out_r}, $pref) if $pref; 498 #now go over the attributes 499 500 while (my $attr = shift(@{$attr_r})) { 501 my $ns = $parser->namespace($attr); 502 if (! $ns) { 503 shift(@{$attr_r}); 504 next; 505 } 506 $pref = $map_ns{$ns}; 507 push( @{$out_r}, $pref) if $pref; 508 shift(@{$attr_r}); 509 } 510 #also add newly defined prefixes 511 my @newNs = $parser->new_ns_prefixes(); 512 for (@newNs) { 513 if ($_ eq '#default') { 514 next; 515 } 516 push (@{$out_r}, $_); 517 } 518} 519 520#The function is called for each child element of dependencies. It finds out the prefixes 521#which are used by the children and which are defined by the parents of <dependencies>. These 522#would be lost when copying the children of <dependencies> into the update information file. 523#Therefore these definitions are collected so that they then can be written in the <dependencies> 524#element of the update information file. 525#param 1: parser 526#param 2: namespace 527#param 3: the @_ received in the start handler 528sub determineNsDefinitions($$$) 529{ 530 my ($parser, $name, $attr_r) = @_; 531 my @attr = @{$attr_r}; 532 533 determineNsDefinitionForItem($parser, $name, 1); 534 535 while (my $attr = shift(@attr)) { 536 determineNsDefinitionForItem($parser, $attr, 0); 537 shift @attr; 538 } 539} 540 541#do not call this function for the element that does not use a prefix 542#param 1: parser 543#param 2: name of the element or attribute 544#param 3: 1 if called for an element name and 0 when called for attribute 545sub determineNsDefinitionForItem($$$) 546{ 547 my ($parser, $name) = @_; 548 my $ns = $parser->namespace($name); 549 if (! $ns) { 550 return; 551 } 552 #If the namespace was not known in <dependencies> then it was defined in one of its children 553 #or in this element. Then we are done since this namespace definition is copied into the 554 #update information. 555 my $bNsKnownInDep; 556 for ( keys %validPrefsInDep) { 557 if ( $validPrefsInDep{$_} eq $ns) { 558 $bNsKnownInDep = 1; 559 last; 560 } 561 } 562 #If the namespace of the current element is known in <dependencies> then check if the same 563 #prefix is used. If not, then the prefix was defined in one of the children of <dependencies> 564 #and was assigned the same namespace. Because we copy of children into the update information, 565 #this definition is also copied. 566 if ($bNsKnownInDep) { 567 #create a map of currently valid prefix/namespace 568 my %curPrefToNs; 569 my @curNs = $parser->current_ns_prefixes(); 570 for (@curNs) { 571 $curPrefToNs{$_} = $parser->expand_ns_prefix($_); 572 } 573 #find the prefix used in <dependencies> to define the namespace of the current element 574 my $validDepPref; 575 for (keys %validPrefsInDep) { 576 if ($validPrefsInDep{$_} eq $ns) { 577 #ignore #default 578 next if $_ eq "#default"; 579 $validDepPref = $_; 580 last; 581 } 582 } 583 #find the prefix defined in the current element used for the namespace of the element 584 my $curPref; 585 for (keys %curPrefToNs) { 586 if ($curPrefToNs{$_} eq $ns) { 587 #ignore #default 588 next if $_ eq "#default"; 589 $curPref = $_; 590 last; 591 } 592 } 593 if ($curPref && $validDepPref && ($curPref eq $validDepPref)) { 594 #If the prefixes and ns are the same, then the prefix definition of <dependencies> or its 595 #parent can be used. However, we need to find out which prefixed are NOT defined in 596 #<dependencies> so we can add them to it when we write the update information. 597 my $bDefined = 0; 598 for (@newPrefsInDep) { 599 if ($curPref eq $_) { 600 $bDefined = 1; 601 last; 602 } 603 } 604 if (! $bDefined) { 605 $notDefInDep{$curPref} = $ns; 606 } 607 } 608 } 609} 610