Subversion Repositories DevTools

Rev

Rev 4697 | Rev 6383 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4625 dpurdie 1
########################################################################
2
# Copyright (c) VIX TECHNOLOGY (AUST) LTD
3
#
4
# Module name   : jats_gen_releasenote.pl
5
# Module type   : Makefile system
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
# Description   : Generate the package Release Note
10
#                 Requires
11
#                   1) Release Note data generated by jats_get_releasenote_data.pl
12
#                   2) Target package in dpkg_archive
13
#                   3) One or more built.files.xxx.xml (optional, but desirable)
14
#
15
#                 Will:
16
#                   Merge built.files.xxx.xml files into the release note data
17
#                   Insert build meta data
18
#                   Save the Release Note MetaData within the package
19
#                   Generate the HTML release note - save within the package
20
#                   Insert Release_Contents into RM Database
21
#
22
# Usage:        See POD
23
#
24
#......................................................................#
25
 
26
require 5.008_002;
27
use strict;
28
use warnings;
29
 
30
use Pod::Usage;
31
use Getopt::Long;
32
use XML::Simple;
33
use File::Path;
34
use File::Copy;
35
use XML::Simple;
36
use Digest::MD5;
37
use File::Find;
38
use FindBin;                                # Determine the current directory
4697 dpurdie 39
use Encode qw(decode encode);
4625 dpurdie 40
 
41
use JatsError;
42
use JatsSystem;
43
use Getopt::Long;
44
use Pod::Usage;
45
use FileUtils;
46
use JatsEnv;
47
use JatsRmApi;
48
 
49
use DBI;
50
 
51
my $VERSION = "1.0.0";                                      # Update this. Inserted into meta data field
52
 
53
# User options
54
my $opt_verbose = 0;
55
my $opt_help = 0;
56
my $opt_archive;
57
my $archive_tag;
58
my $opt_pname;
59
my $opt_pversion;
60
my $opt_release_note;
61
my $opt_pvid;
62
my $opt_outname;
63
my $opt_outputXml;
64
my $opt_updateRmFiles;
65
my $opt_workingDir;
66
 
67
#   Global vars
68
our $GBE_HOSTNAME;
69
my $DPKG_ROOT;
70
my $DPKG_DOC;
71
my $JATS_RN;
72
my $OUT_ROOT;
73
 
74
 
75
#
76
#   Structure to translate -archive=xxx option to archive variable
77
#   These are the various dpkg_archives known to JATS
78
#
79
my %Archive2Var =( 'main'       =>  'GBE_DPKG',
80
                   'store'      =>  'GBE_DPKG_STORE',
81
                   'cache'      =>  'GBE_DPKG_CACHE',
82
                   'local'      =>  'GBE_DPKG_LOCAL',
83
                   'sandbox'    =>  'GBE_DPKG_SBOX',
84
                   'deploy'     =>  'GBE_DPLY',
85
                   );
86
 
87
#-------------------------------------------------------------------------------
88
# Function        : Main Entry
89
#
90
# Description     :
91
#
92
# Inputs          :
93
#
94
# Returns         :
95
#
96
{
97
    my $result = GetOptions (
98
                    "help+"         => \$opt_help,              # flag, multiple use allowed
99
                    "manual:3"      => \$opt_help,              # flag, set value
100
                    "verbose:+"     => \$opt_verbose,           # flag, increment
101
                    "archive=s"     => \$opt_archive,           # string
102
                    "releasenote=s" => \$opt_release_note,      # string
103
                    "UpdateRmFiles" => \$opt_updateRmFiles,     # flag
104
                    "WorkDir=s"     => \$opt_workingDir,        # String
105
                    );
106
 
107
    #
108
    #   Process help and manual options
109
    #
110
    pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result);
111
    pod2usage(-verbose => 1)  if ($opt_help == 2 );
112
    pod2usage(-verbose => 2)  if ($opt_help > 2);
113
 
114
    ErrorConfig( 'name'    =>'GenRn', 'verbose' => $opt_verbose );
115
    InitFileUtils();
116
 
117
    #
118
    #   Needed EnvVars
119
    #
120
    EnvImport ('GBE_HOSTNAME');
121
    $JATS_RN = $FindBin::Bin;
122
    Error("Release Note tools not found", $JATS_RN)
123
        unless (-d $JATS_RN);
124
 
125
 
126
    #
127
    #   Sanity Check
128
    #
129
    Error("Release Note data not provided") unless $opt_release_note;
130
    Error("Release Note data not found") unless -f $opt_release_note;
131
    if (defined $opt_workingDir)
132
    {
133
        $opt_workingDir = FullPath($opt_workingDir);
134
        Error("Working directory is not a directory") unless -d $opt_workingDir;
135
        Error("Working directory is writable") unless -w $opt_workingDir;
136
    }
137
 
138
    #
139
    #   Determine the target archive
140
    #   The default archive is GBE_DPKG, but this may be changed
141
    #
142
    $opt_archive = 'main' unless ( $opt_archive );
143
    my $archive_tag = $Archive2Var{$opt_archive};
144
    Error("Unknown archive specified: $opt_archive")
145
        unless ( $archive_tag );
146
    $DPKG_ROOT = $ENV{$archive_tag} || '';
147
    Verbose ("Archive Variable: $archive_tag" );
148
    Verbose2 ("Archive Path: $DPKG_ROOT" );
149
 
150
    #
151
    #   Process the release note and extract essential information
152
    #
153
    processReleaseNote();
154
 
155
    #
156
    #   Locate the target package
157
    #
158
    $DPKG_ROOT = catdir($DPKG_ROOT, $opt_pname, $opt_pversion);
159
    Verbose ("Package Path: ", DisplayPath($DPKG_ROOT) );
160
    Error ("Package not found in archive", $DPKG_ROOT) unless -d $DPKG_ROOT;
161
 
162
    #   OUT_ROOT is really only used for testing
163
    #   Needs to mimic $DPKG_ROOT
164
    $OUT_ROOT = defined($opt_workingDir) ? $opt_workingDir : $DPKG_ROOT; 
165
    Verbose2 ("Output Path: $OUT_ROOT" );
166
 
167
    #
168
    #   Calculate output filenames
169
    #   Html Release Note:
170
    #       RELEASE_NOTES_PVID_PKGNAME_CLEANV.html
171
    #
172
    $DPKG_DOC = catdir($OUT_ROOT, 'doc');
173
    $opt_outputXml = catdir($DPKG_DOC, 'release_note.xml');
174
 
175
    my $cleanv = $opt_pversion;
176
    $cleanv =~ s~\.~_~g;
177
    $opt_outname = join('_',
178
                        'RELEASE_NOTES',
179
                        $opt_pvid,
180
                        $opt_pname,
181
                        $cleanv,    
182
                        ) . '.html';
183
    Verbose("Note Name: $opt_outname");
184
 
185
    #
186
    #   Locate the release note data file
187
    #   This was created as a prerequisite to the build
188
    #   Note: windows requires '/' in the file list
189
    #
190
    my @filesList;
191
    foreach my $item ( glob(catdir($DPKG_ROOT, 'built.files.*.xml'))) {
192
        $item =~ s~\\~/~g;
193
        push @filesList, $item;
194
    }
195
    unless (scalar @filesList > 0)
196
    {
4697 dpurdie 197
        Warning("No file list found within the package: $opt_pname/$opt_pversion");
4625 dpurdie 198
        #
199
        #   No filelist found in the package
200
        #   This may occur for packages that did not go though the build system
201
        #   Create a package-list
202
        #
203
        my $item = generateFileList();
204
        $item =~ s~\\~/~g;
205
        push @filesList, $item;
206
    }
207
 
208
    #
209
    #   Create output directory within the package
210
    #
211
    mkpath($DPKG_DOC, 0, 0775);
212
 
213
    #
214
    #   Merge the Release Note and the various file lists into a single
215
    #   XML file. Keep the file local
216
    #
217
    System('--NoShell', '--Exit', 'java', '-jar', 
218
           catdir($JATS_RN, 'saxon9he.jar'), 
219
           '-xsl:' . catdir($JATS_RN, 'merge.xslt'),
220
           "-s:$opt_release_note",
221
           'fileList=' . join(',', @filesList),
222
           '-o:' . 'built.files.releasenote.xml'
223
            );
224
 
225
    #
226
    #   Generate the HTML Release Note
227
    #   Output directly to the body of the package
228
    #
229
    System('--NoShell', '--Exit', 'java', '-jar', 
230
           catdir($JATS_RN, 'saxon9he.jar'), 
231
           '-xsl:' . catdir($JATS_RN, 'releaseNote2html.xslt'),
232
           '-s:' . 'built.files.releasenote.xml',
233
           "-o:" . catdir($DPKG_DOC, $opt_outname)
234
            );
235
 
236
    #
237
    #   Transfer the XML database directly into the package
238
    #
239
    unless( File::Copy::copy('built.files.releasenote.xml', $opt_outputXml) )
240
    {
241
        Error("Cannot transfer XML release note", $!, $opt_outputXml);
242
    }
243
 
244
    #
245
    #   Delete the built.files.xxx.xml that may be present in the root of the package
246
    #   These are no longer needed - they have been incorporated into the release_note.xml file
247
    #
248
    foreach my $item ( glob(catdir($DPKG_ROOT, 'built.files.*.xml'))) {
249
        unlink $item;
250
    }
251
 
252
    #
253
    #   Update the Release Manager entry - File Data
254
    #
255
    if ($opt_updateRmFiles)
256
    {
257
        updateRmFiles();
258
        updateRmNoteInfo();
259
    }
260
 
261
    #
262
    #   All done
263
    #
264
    Verbose ("Release Note:", DisplayPath(catdir($DPKG_DOC, $opt_outname)));
265
    exit 0;
266
}
267
 
268
#-------------------------------------------------------------------------------
269
# Function        : processReleaseNote 
270
#
271
# Description     : Extract essential information from the Release Note Data
272
#
273
# Inputs          : 
274
#
275
# Returns         : Populates global variables 
276
#
277
sub processReleaseNote
278
{
279
    my $xml = XMLin($opt_release_note);
280
 
281
    foreach my $item ( qw (name version pvid) ) {
282
        Error("Unexpected ReleaseNote format: package $item")
283
            unless (exists $xml->{package}{$item});
284
    }
285
 
286
    $opt_pname = $xml->{package}{name};
287
    $opt_pversion = $xml->{package}{version};
288
    $opt_pvid = $xml->{package}{pvid};
289
 
290
    Verbose("Package Name: $opt_pname") ;
291
    Verbose("Package Version: $opt_pversion") ;
292
    Verbose("Package Pvid: $opt_pvid") ;
293
}
294
 
295
#-------------------------------------------------------------------------------
296
# Function        : generateFileList
297
#                   generateFileListProc 
298
#
299
# Description     : A fileList has not been found in the target package
300
#                   Perhaps the package was not created by the build system
301
#                   
302
#                   Generate a fileList        
303
#
304
# Inputs          : None
305
#
306
# Returns         : Path to the generated file list
307
#
308
my @generateFileListData;
309
my $baseLength;
310
sub generateFileList
311
{
312
    my $outXmlFile;
313
    Verbose("Generate internal filelist - none found in package");
314
 
315
    #
316
    #   Scan the package and process each file
317
    #
318
    $baseLength = length($DPKG_ROOT);
319
    File::Find::find( \&generateFileListProc, $DPKG_ROOT );
320
 
321
    #
322
    #   Write out XML of the Generated file list
323
    #
324
    my $data;
325
    $data->{file} = \@generateFileListData;
326
 
327
    #
328
    #   Write out sections of XML
329
    #       Want control over the output order
330
    #       Use lots of attributes and only elements for arrays
331
    #       Save as one attribute per line - for readability
332
    #
333
    $outXmlFile = catdir($OUT_ROOT,"built.files.$GBE_HOSTNAME.xml");
334
 
335
    Verbose2('Meta File', $outXmlFile);
336
    my $xs = XML::Simple->new( NoAttr =>0, AttrIndent => 1 );
337
 
338
    open (my $XML, '>', $outXmlFile) || Error ("Cannot create output file: $outXmlFile", $!);
339
    $xs->XMLout($data, 
340
                'RootName' => 'files', 
341
                'XMLDecl'  => '<?xml version="1.0" encoding="UTF-8"?>',
342
                'OutputFile' => $XML);
343
    close $XML;
344
 
345
    return $outXmlFile;
346
}
347
 
348
sub generateFileListProc
349
{
350
    my %data;
351
    my $md5sum;
352
    my $source = $File::Find::name;
353
    my $type = 'dir';
354
 
355
    my $target = substr($source, $baseLength);
356
    $target =~ s~^/~~;
357
    $target =~ s~/$~~;
358
    Verbose2("GenFileList: $source, $target");
359
    return if (length ($target) == 0);
360
 
361
    if (-l $source)
362
    {
363
        $type = 'link';
364
    }
365
    elsif ( -f $source)
366
    {
367
        $type = 'file';
368
        Verbose2("Calculate MD5 Digest: $source");
369
        open(my $fh , $source) or Error ("Can't open '$source': $!");
370
        binmode $fh, ':crlf';
371
        $md5sum = Digest::MD5->new->addfile($fh)->hexdigest;
372
        close $fh;
373
    }
374
 
4697 dpurdie 375
    #
376
    #   Convert from iso-8859-1 into utf-8
377
    #
378
    $target = decode( 'iso-8859-1', $target );
379
    $target = encode( 'utf-8', $target );
4625 dpurdie 380
 
381
    if (-d $source)
382
    {
383
        $data{path} = $target;
384
    }
385
    else
386
    {
387
        $data{path} = StripFileExt($target);
388
        $data{name} = StripDir($target);
389
        if (defined $md5sum)
390
        {
391
            $data{size} = (stat($source))[7];
392
            $data{md5sum} = $md5sum;
393
        }
394
    }
395
 
396
    $data{fullname} = $target;
397
    $data{type} = $type;
398
    $data{machtype} = 'unknown';
399
    $data{host} = $GBE_HOSTNAME;
400
 
401
    # Put a nice '/' on the end of the patch elements
402
    $data{path} .= '/'
403
        if ( exists ($data{path}) && length($data{path}) > 0);
404
 
405
    push @generateFileListData, \%data;
406
}
407
 
408
#-------------------------------------------------------------------------------
409
# Function        : updateRmFiles 
410
#
411
# Description     : 
412
#
413
# Inputs          : 
414
#
415
# Returns         : 
416
#
417
my $RM_DB;
418
my  $updateRmFilesData;
419
sub updateRmFiles
420
{
421
    my $eCount = 0;
422
    #
423
    #   If in use the the autobuilder - which it should be, then
424
    #   modify the user name to access RM with a proxy user. This is the
425
    #   same method used in the 'buildtool'
426
    #
427
    if ($ENV{GBE_ABT} && $ENV{GBE_RM_USERNAME} && $ENV{GBE_RM_USERNAME} !~ m~]$~ )
428
    {
429
        Verbose("Access RM database as proxy user");
430
        $ENV{GBE_RM_USERNAME} = $ENV{GBE_RM_USERNAME} . '[release_manager]'; 
431
    }
432
 
433
    #
434
    #   Read in the release note data base
435
    #
436
    my $rnDataBase = catdir($DPKG_DOC, 'release_note.xml');
437
    if (! -f $rnDataBase )
438
    {
439
        Error("Release Note XML database not found: $!", $rnDataBase);
440
    }
441
    my $xml = XMLin($rnDataBase);
442
 
443
    #
444
    #   Sanity Test the data
445
    #
446
    Error("Sanity Check Failure. PVID") unless($opt_pvid eq $xml->{package}{pvid});
447
    Error("Sanity Check Failure. Name") unless($opt_pname eq $xml->{package}{name});
448
    Error("Sanity Check Failure. Version") unless($opt_pversion eq $xml->{package}{version});
449
    Error("Sanity Check Failure. File Section") unless(exists $xml->{files}{file});
450
    #DebugDumpData("XML DATA", \$xml);
451
 
452
    #
453
    #   Delete any existing entry(s)
454
    #
455
    updateRmFilesDelete();
456
 
457
    #
458
    #   Scan each entry and pump required data into the RM Database
459
    #   The {files}{file} may be either an array or a hash XMLin appears to
460
    #   make some decision as to which it will be, and I can't control it
461
    #
4697 dpurdie 462
    #   If its a HASH, then it may be a HASH of HASH or a single entry
463
    #
4625 dpurdie 464
    #   When it does happen the hash key is the 'name' element
465
    #
466
    if (ref($xml->{files}{file}) eq 'HASH')
467
    {
468
        Verbose("Convert file hash to an array");
469
        my @nowArray;
470
        foreach my $name ( keys %{$xml->{files}{file}})
471
        {
472
            my $entry = $xml->{files}{file}{$name};
4697 dpurdie 473
            if (ref($entry) eq 'HASH')
474
            {
475
                $entry->{name} = $name;
476
                push @nowArray, $entry;
477
            }
478
            else
479
            {
480
                # Its a single entry, not a HASH of entries
481
                push @nowArray, $xml->{files}{file};
482
                last; 
483
            }
4625 dpurdie 484
        }
485
        $xml->{files}{file} = \@nowArray
486
    }
487
    Error("Sanity Check Failure. File Section not an ARRAY") unless(ref($xml->{files}{file}) eq 'ARRAY');
488
 
489
    #
490
    #   Release Manager Database will barf if there are duplicate entries
491
    #   Maintain a hash of items processed, and only process each once
492
    #
493
    my %fullNameSeen;
494
 
495
    $updateRmFilesData = ""; 
496
    foreach my $entry (@{$xml->{files}{file}})
497
    {
498
        #
499
        #   Ignore 'merge' entries
500
        #
501
        next if ((exists $entry->{type}) && ($entry->{type} eq 'merge'));
502
        #
503
        #   Clean up the data
504
        #
505
        my $fname = $entry->{name} || '';
506
        my $fpath  = $entry->{path} || '';
507
        my $fsize  = $entry->{size} || 0;
508
        my $fmd5  = $entry->{md5sum} || '';
509
        $fpath =~ s~/$~~ unless $fname;
510
 
511
        unless( $fullNameSeen{$fpath}{$fname} )
512
        {
513
            $fullNameSeen{$fpath}{$fname} = 1;
514
 
515
            #
516
            #   Create SQL fragment for the insert
517
            #
518
            $eCount++;
6116 dpurdie 519
            my $quoteFname = $fname;
520
            $quoteFname =~ s~'~''~g;
4625 dpurdie 521
            my $entry = " INTO release_manager.release_components ( pv_id, file_name, file_path, byte_size, crc_cksum, crc_modcrc ) " .
6116 dpurdie 522
                        " VALUES ( $opt_pvid, '$quoteFname', '$fpath',$fsize , '$fmd5', '')";
4625 dpurdie 523
            $updateRmFilesData .= $entry;
524
 
525
            # The size of the aggregation is key to performance
526
            # Too big is as bad as too small
527
            if (length($updateRmFilesData) > 10000)
528
            {
529
                updateRmFilesInsert();
530
            }
531
        }
532
        else
533
        {
534
            Warning("Multiple file entries for: $fpath $fname");
535
        }
536
    }
537
    updateRmFilesInsert();
538
    Verbose ("Inserted $eCount entries into Release_Components");
539
}
540
 
541
#-------------------------------------------------------------------------------
542
# Function        : updateRmFilesInsert 
543
#
544
# Description     : Insert entries using the partial SQL statement 
545
#                   Must be called when the partial SQL buffer get large
546
#                   as well as at the end to fluch any outstanding inserts
547
#
548
# Inputs          : 
549
#
550
# Returns         : 
551
#
552
sub updateRmFilesInsert
553
{
554
    if (length($updateRmFilesData) > 0)
555
    {
556
        executeRmQuery(
557
            'updateRmFilesInsert',
6116 dpurdie 558
            'INSERT ALL' . $updateRmFilesData . ' SELECT 1 FROM DUAL'
4625 dpurdie 559
            );
560
 
561
        $updateRmFilesData = ""; 
562
    }
563
}
564
#-------------------------------------------------------------------------------
565
# Function        : updateRmFilesDelete 
566
#
567
# Description     : Delete ALL entires in the 'release_components' table associated
568
#                   with the current pvid
569
#                   
570
#                   Needs to be done before new entries are added.    
571
#
572
# Inputs          : 
573
#
574
# Returns         : 
575
#
576
sub updateRmFilesDelete
577
{
578
    executeRmQuery(
579
        'updateRmFilesDelete',
580
        'delete from release_manager.release_components where pv_id=' . $opt_pvid
581
        );
582
}
583
 
584
#-------------------------------------------------------------------------------
585
# Function        : updateRmNoteInfo 
586
#
587
# Description     : Insert Release Note Info into the Release Manager Database
588
#                   This has the side effect that RM will see the Release Note
589
#                   as being fully generated.
590
#
591
# Inputs          : 
592
#
593
# Returns         : 
594
#
595
sub updateRmNoteInfo
596
{
597
    #
598
    #   Determine the path to the Release Note in a form that is suitable for the Release Manager
599
    #   Database. This expects: /dpkg_archive/PkgName/PkgVer/doc/pkgFileName
600
    #
601
    my $rnPath = join('/', '', 'dpkg_archive', $opt_pname, $opt_pversion, 'doc', $opt_outname);
602
    Verbose("RN_INFO:", $rnPath);
603
 
604
    # Into the database
605
    executeRmQuery (
606
        'updateRmNoteInfo', 
607
        'update release_manager.package_versions set release_notes_info=\'' . $rnPath . '\' where pv_id=' . $opt_pvid
608
        );
609
}
610
 
611
#-------------------------------------------------------------------------------
612
# Function        : executeRmQuery 
613
#
614
# Description     : Execute a simple RM query. One that does not expect any return data
615
#
616
# Inputs          : $fname           - OprName, for error reporting
617
#                   $m_sqlstr        - SQL String
618
#
619
# Returns         : Will exit on error
620
#
621
sub executeRmQuery
622
{
623
    my ($fname, $m_sqlstr) = @_;
624
 
625
    #
626
    #   Connect to the Database - once
627
    #
628
    connectRM(\$RM_DB, 0) unless $RM_DB;
629
 
630
    Verbose2('ExecuteQuery:', $fname);
631
    #
632
    #   Create the full SQL statement
633
    #
634
    my $sth = $RM_DB->prepare($m_sqlstr);
635
    if ( defined($sth) )
636
    {
637
        if ( $sth->execute() )
638
        {
639
            $sth->finish();
640
        }
641
        else
642
        {
643
            Error("$fname: Execute failure: $m_sqlstr", $sth->errstr() );
644
        }
645
    }
646
    else
647
    {
648
        Error("$fname: Prepare failure");
649
    }
650
}
651
 
652
 
653
#-------------------------------------------------------------------------------
654
#   Documentation
655
#
656
 
657
=pod
658
 
659
=for htmltoc    SYSUTIL::
660
 
661
=head1 NAME
662
 
663
jats_gen_releasenote - Generate a Release Note
664
 
665
=head1 SYNOPSIS
666
 
667
 jats jats_gen_releasenote [options]
668
 
669
 Options:
670
    -help              - Brief help message
671
    -help -help        - Detailed help message
672
    -man               - Full documentation
673
    -verbose           - Display additional progress messages
674
    -outfile=name      - [Optional] Name of the output XML file
675
    -archive=name      - [Optional] Name package archive
676
    -releasenote=path  - Path to the Release Note Data
677
    -UpdateRmFiles     - Update the Files list in Release Manager
678
 
679
 
680
=head1 OPTIONS
681
 
682
=over 8
683
 
684
=item B<-help>
685
 
686
Print a brief help message and exits.
687
 
688
=item B<-help -help>
689
 
690
Print a detailed help message with an explanation for each option.
691
 
692
=item B<-man>
693
 
694
Prints the manual page and exits.
695
 
696
=item B<-pvid=nn>
697
 
698
This option provides identifies the PackageVersion to be processed.
699
 
700
This option is mandatory.
701
 
702
=item B<-outfile=name>
703
 
704
This option specifies the output file name.
705
 
706
If not provided by the user the output filename will be created in the current directory
707
and it will be named after the package name and package version.
708
 
709
If the filename does not end in .xml, then .xml will be appended to the file name.
710
 
711
=item B<-UpdateRmFiles>
712
 
713
This option will case the utility to ppdate the Files list in Release Manager.
714
 
715
The existing file list will be replaced by the one within the package.
716
 
717
Note: Write Access to Release Manager is required.
718
 
719
=back
720
 
721
=head1 DESCRIPTION
722
 
723
This utility program is used to extract sufficient information from Release Manager and other
724
associated databases so that a Release Note can be created.
725
 
726
The extracted data is stored in an XML format. The intent is that XSLT will be used to create
727
an HTML based release note.
728
 
729
 
730
=head1 EXAMPLE
731
 
732
=head2 jats jats_gen_releasenote -releasenote=/tmp/myreleasenote.xml
733
 
734
This will process release note data in the specified XML file.
735
 
736
=cut