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