View | Details | Raw Unified | Return to bug 15108
Collapse All | Expand All

(-)a/installer/data/mysql/kohastructure.sql (-1 / +3 lines)
Lines 209-214 CREATE TABLE `biblioitems` ( -- information related to bibliographic records in Link Here
209
  KEY `isbn` (`isbn`(255)),
209
  KEY `isbn` (`isbn`(255)),
210
  KEY `issn` (`issn`(255)),
210
  KEY `issn` (`issn`(255)),
211
  KEY `publishercode` (`publishercode`),
211
  KEY `publishercode` (`publishercode`),
212
  KEY `timestamp_bibno` (`timestamp`, `biblionumber`),
212
  CONSTRAINT `biblioitems_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE
213
  CONSTRAINT `biblioitems_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE
213
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
214
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
214
215
Lines 866-872 CREATE TABLE `deletedbiblioitems` ( -- information about bibliographic records t Link Here
866
  KEY `bibnoidx` (`biblionumber`),
867
  KEY `bibnoidx` (`biblionumber`),
867
  KEY `itemtype_idx` (`itemtype`),
868
  KEY `itemtype_idx` (`itemtype`),
868
  KEY `isbn` (`isbn`(255)),
869
  KEY `isbn` (`isbn`(255)),
869
  KEY `publishercode` (`publishercode`)
870
  KEY `publishercode` (`publishercode`),
871
  KEY `timestamp_bibno` (`timestamp`, `biblionumber`)
870
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
872
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
871
873
872
--
874
--
(-)a/opac/oai.pl (-104 / +191 lines)
Lines 1-27 Link Here
1
#!/usr/bin/perl
1
#!/usr/bin/perl
2
2
3
# Copyright Biblibre 2008
3
# Copyright Biblibre 2008
4
# Copyright The National Library of Finland 2015
4
#
5
#
5
# This file is part of Koha.
6
# This file is part of Koha.
6
#
7
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# Koha is free software; you can redistribute it and/or modify it
8
# terms of the GNU General Public License as published by the Free Software
9
# under the terms of the GNU General Public License as published by
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# the Free Software Foundation; either version 3 of the License, or
10
# version.
11
# (at your option) any later version.
11
#
12
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# Koha is distributed in the hope that it will be useful, but
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
15
#
17
#
16
# You should have received a copy of the GNU General Public License along
18
# You should have received a copy of the GNU General Public License
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
19
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
20
21
21
use strict;
22
use strict;
22
use warnings;
23
use warnings;
23
24
24
use CGI qw/:standard -oldstyle_urls/;
25
use CGI qw( :standard -oldstyle_urls -utf8 );
25
use vars qw( $GZIP );
26
use vars qw( $GZIP );
26
use C4::Context;
27
use C4::Context;
27
28
Lines 59-64 else { Link Here
59
}
60
}
60
61
61
binmode STDOUT, ':encoding(UTF-8)';
62
binmode STDOUT, ':encoding(UTF-8)';
63
# OAI-PMH handles dates in UTC, so do that on the database level to avoid the
64
# need for any conversions
65
C4::Context->dbh->prepare("SET time_zone='+00:00'")->execute();
62
my $repository = C4::OAI::Repository->new();
66
my $repository = C4::OAI::Repository->new();
63
67
64
# __END__ Main Prog
68
# __END__ Main Prog
Lines 71-77 my $repository = C4::OAI::Repository->new(); Link Here
71
# - from
75
# - from
72
# - until
76
# - until
73
# - offset
77
# - offset
74
# 
78
#
75
package C4::OAI::ResumptionToken;
79
package C4::OAI::ResumptionToken;
76
80
77
use strict;
81
use strict;
Lines 86-94 sub new { Link Here
86
90
87
    my $self = $class->SUPER::new(%args);
91
    my $self = $class->SUPER::new(%args);
88
92
89
    my ($metadata_prefix, $offset, $from, $until, $set);
93
    my ($metadata_prefix, $offset, $from, $until, $set, $stage);
90
    if ( $args{ resumptionToken } ) {
94
    if ( $args{ resumptionToken } ) {
91
        ($metadata_prefix, $offset, $from, $until, $set)
95
        ($metadata_prefix, $offset, $from, $until, $set, $stage)
92
            = split( '/', $args{resumptionToken} );
96
            = split( '/', $args{resumptionToken} );
93
    }
97
    }
94
    else {
98
    else {
Lines 101-106 sub new { Link Here
101
        }
105
        }
102
        $offset = $args{ offset } || 0;
106
        $offset = $args{ offset } || 0;
103
        $set = $args{set};
107
        $set = $args{set};
108
        $stage = (defined $args{stage}) ? $args{stage} : 0;
104
    }
109
    }
105
110
106
    $self->{ metadata_prefix } = $metadata_prefix;
111
    $self->{ metadata_prefix } = $metadata_prefix;
Lines 108-116 sub new { Link Here
108
    $self->{ from            } = $from;
113
    $self->{ from            } = $from;
109
    $self->{ until           } = $until;
114
    $self->{ until           } = $until;
110
    $self->{ set             } = $set;
115
    $self->{ set             } = $set;
116
    $self->{ stage           } = $stage;
111
117
112
    $self->resumptionToken(
118
    $self->resumptionToken(
113
        join( '/', $metadata_prefix, $offset, $from, $until, $set ) );
119
        join( '/', $metadata_prefix, $offset, $from, $until, $set, $stage ) );
114
    $self->cursor( $offset );
120
    $self->cursor( $offset );
115
121
116
    return $self;
122
    return $self;
Lines 138-146 sub new { Link Here
138
        repositoryName      => C4::Context->preference("LibraryName"),
144
        repositoryName      => C4::Context->preference("LibraryName"),
139
        adminEmail          => C4::Context->preference("KohaAdminEmailAddress"),
145
        adminEmail          => C4::Context->preference("KohaAdminEmailAddress"),
140
        MaxCount            => C4::Context->preference("OAI-PMH:MaxCount"),
146
        MaxCount            => C4::Context->preference("OAI-PMH:MaxCount"),
141
        granularity         => 'YYYY-MM-DD',
147
        granularity         => 'YYYY-MM-DDThh:mm:ssZ',
142
        earliestDatestamp   => '0001-01-01',
148
        earliestDatestamp   => '0001-01-01T00:00:00Z',
143
        deletedRecord       => 'no',
149
        deletedRecord       => 'persistent',
144
    );
150
    );
145
151
146
    # FIXME - alas, the description element is not so simple; to validate
152
    # FIXME - alas, the description element is not so simple; to validate
Lines 187-192 sub new { Link Here
187
            metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/'
193
            metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/'
188
        ) );
194
        ) );
189
        $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
195
        $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
196
            metadataPrefix    => 'marc21',
197
            schema            => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim.xsd',
198
            metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim'
199
        ) );
200
        $self->metadataFormat( HTTP::OAI::MetadataFormat->new(
190
            metadataPrefix    => 'marcxml',
201
            metadataPrefix    => 'marcxml',
191
            schema            => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim.xsd',
202
            schema            => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim.xsd',
192
            metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim'
203
            metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim'
Lines 210-216 use HTTP::OAI::Metadata::OAI_DC; Link Here
210
use base ("HTTP::OAI::Record");
221
use base ("HTTP::OAI::Record");
211
222
212
sub new {
223
sub new {
213
    my ($class, $repository, $marcxml, $timestamp, $setSpecs, %args) = @_;
224
    my ($class, $repository, $marcxml, $timestamp, $setSpecs, $deleted, %args) = @_;
214
225
215
    my $self = $class->SUPER::new(%args);
226
    my $self = $class->SUPER::new(%args);
216
227
Lines 218-239 sub new { Link Here
218
    $self->header( new HTTP::OAI::Header(
229
    $self->header( new HTTP::OAI::Header(
219
        identifier  => $args{identifier},
230
        identifier  => $args{identifier},
220
        datestamp   => $timestamp,
231
        datestamp   => $timestamp,
232
        status => $deleted ? 'deleted' : undef
221
    ) );
233
    ) );
222
234
223
    foreach my $setSpec (@$setSpecs) {
235
    foreach my $setSpec (@$setSpecs) {
224
        $self->header->setSpec($setSpec);
236
        $self->header->setSpec($setSpec);
225
    }
237
    }
226
238
227
    my $parser = XML::LibXML->new();
239
    if (!$deleted) {
228
    my $record_dom = $parser->parse_string( $marcxml );
240
        my $parser = XML::LibXML->new();
229
    my $format =  $args{metadataPrefix};
241
        my $record_dom = $parser->parse_string( $marcxml );
230
    if ( $format ne 'marcxml' ) {
242
        my $format =  $args{metadataPrefix};
231
        my %args = (
243
        if ( $format ne 'marcxml' && $format ne 'marc21' ) {
232
            OPACBaseURL => "'" . C4::Context->preference('OPACBaseURL') . "'"
244
            my %args = (
233
        );
245
                OPACBaseURL => "'" . C4::Context->preference('OPACBaseURL') . "'"
234
        $record_dom = $repository->stylesheet($format)->transform($record_dom, %args);
246
            );
247
            $record_dom = $repository->stylesheet($format)->transform($record_dom, %args);
248
        }
249
        $self->metadata( HTTP::OAI::Metadata->new( dom => $record_dom ) );
235
    }
250
    }
236
    $self->metadata( HTTP::OAI::Metadata->new( dom => $record_dom ) );
237
251
238
    return $self;
252
    return $self;
239
}
253
}
Lines 314-353 sub new { Link Here
314
    if(defined $token->{'set'}) {
328
    if(defined $token->{'set'}) {
315
        $set = GetOAISetBySpec($token->{'set'});
329
        $set = GetOAISetBySpec($token->{'set'});
316
    }
330
    }
317
    my $sql = "
331
    my $max = $repository->{'koha_max_count'};
318
        SELECT biblioitems.biblionumber, biblioitems.timestamp
332
    my $offset = $token->{'offset'};
319
        FROM biblioitems
333
320
    ";
334
    my $count = 0;
321
    $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
335
    # Since creating a union of normal and deleted record tables would be a heavy
322
    $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? ";
336
    # operation, build results in two stages: first deleted records, then normal records
323
    $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
337
    MAINLOOP:
324
    $sql .= "
338
    for ( my $stage = $token->{'stage'}; $stage < 2; $stage++ ) {
325
        LIMIT $repository->{'koha_max_count'}
339
        my $sql = C4::OAI::Utils->build_list_sql($stage == 0, $set, $max, $offset);
326
        OFFSET $token->{'offset'}
340
        my $sth = $dbh->prepare( $sql ) || die('Query preparation failed: ' . $dbh->errstr);
327
    ";
341
        my @bind_params = (C4::OAI::Utils->fix_date($token->{'from'}, 0), C4::OAI::Utils->fix_date($token->{'until'}, 1));
328
    my $sth = $dbh->prepare( $sql );
342
        push @bind_params, $set->{'id'} if defined $set;
329
    my @bind_params = ($token->{'from'}, $token->{'until'});
343
        $sth->execute( @bind_params ) || die('Query failed: ' . $dbh->errstr);
330
    push @bind_params, $set->{'id'} if defined $set;
344
331
    $sth->execute( @bind_params );
345
        while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) {
332
346
            $count++;
333
    my $pos = $token->{offset};
347
            if ( $count > $max ) {
334
    while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) {
348
                $self->resumptionToken(
335
        $timestamp =~ s/ /T/, $timestamp .= 'Z';
349
                    new C4::OAI::ResumptionToken(
336
        $self->identifier( new HTTP::OAI::Header(
350
                        metadataPrefix  => $token->{metadata_prefix},
337
            identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
351
                        from            => $token->{from},
338
            datestamp  => $timestamp,
352
                        until           => $token->{until},
339
        ) );
353
                        offset          => $offset + $max,
340
        $pos++;
354
                        set             => $token->{set},
355
                        stage           => $stage
356
                    )
357
                );
358
                last MAINLOOP;
359
            }
360
            $timestamp =~ s/ /T/, $timestamp .= 'Z';
361
            my $header = new HTTP::OAI::Header(
362
                identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
363
                datestamp  => $timestamp,
364
            );
365
            if ($stage == 0) {
366
                $header->status( 'deleted' );
367
            }
368
            $self->identifier( $header );
369
        }
370
        # Reset offset for next stage
371
        $offset = 0;
341
    }
372
    }
342
    $self->resumptionToken(
343
        new C4::OAI::ResumptionToken(
344
            metadataPrefix  => $token->{metadata_prefix},
345
            from            => $token->{from},
346
            until           => $token->{until},
347
            offset          => $pos,
348
            set             => $token->{set}
349
        )
350
    ) if ($pos > $token->{offset});
351
373
352
    return $self;
374
    return $self;
353
}
375
}
Lines 467-518 sub new { Link Here
467
    if(defined $token->{'set'}) {
489
    if(defined $token->{'set'}) {
468
        $set = GetOAISetBySpec($token->{'set'});
490
        $set = GetOAISetBySpec($token->{'set'});
469
    }
491
    }
470
    my $sql = "
492
    my $offset = $token->{'offset'};
471
        SELECT biblioitems.biblionumber, biblioitems.marcxml, biblioitems.timestamp
493
    my $max = $repository->{'koha_max_count'};
472
        FROM biblioitems
494
    my $query = '
473
    ";
495
SELECT biblioitems.marcxml
474
    $sql .= " JOIN oai_sets_biblios ON biblioitems.biblionumber = oai_sets_biblios.biblionumber " if defined $set;
496
FROM biblioitems
475
    $sql .= " WHERE DATE(timestamp) >= ? AND DATE(timestamp) <= ? ";
497
WHERE biblionumber = ?
476
    $sql .= " AND oai_sets_biblios.set_id = ? " if defined $set;
498
';
477
    $sql .= "
499
    my $marc_sth = $dbh->prepare($query) || die('Query preparation failed: ' . $dbh->errstr);
478
        LIMIT $repository->{'koha_max_count'}
500
479
        OFFSET $token->{'offset'}
501
    my $count = 0;
480
    ";
502
    # Since creating a union of normal and deleted record tables would be a heavy
481
503
    # operation, build results in two stages: first deleted records, then normal records
482
    my $sth = $dbh->prepare( $sql );
504
    MAINLOOP:
483
    my @bind_params = ($token->{'from'}, $token->{'until'});
505
    for ( my $stage = $token->{'stage'}; $stage < 2; $stage++ ) {
484
    push @bind_params, $set->{'id'} if defined $set;
506
        my $sql = C4::OAI::Utils->build_list_sql($stage == 0, $set, $max, $offset);
485
    $sth->execute( @bind_params );
507
        my $sth = $dbh->prepare( $sql ) || die('Query preparation failed: ' . $dbh->errstr);
486
508
        my @bind_params = (C4::OAI::Utils->fix_date($token->{'from'}, 0), C4::OAI::Utils->fix_date($token->{'until'}, 1));
487
    my $pos = $token->{offset};
509
        push @bind_params, $set->{'id'} if defined $set;
488
    while ( my ($biblionumber, $marcxml, $timestamp) = $sth->fetchrow ) {
510
        $sth->execute( @bind_params ) || die('Query failed: ' . $dbh->errstr);
489
        my $oai_sets = GetOAISetsBiblio($biblionumber);
511
490
        my @setSpecs;
512
        while ( my ($biblionumber, $timestamp) = $sth->fetchrow ) {
491
        foreach (@$oai_sets) {
513
            $count++;
492
            push @setSpecs, $_->{spec};
514
            if ( $count > $max ) {
515
                $self->resumptionToken(
516
                    new C4::OAI::ResumptionToken(
517
                        metadataPrefix  => $token->{metadata_prefix},
518
                        from            => $token->{from},
519
                        until           => $token->{until},
520
                        offset          => $offset + $max,
521
                        set             => $token->{set},
522
                        stage           => $stage
523
                    )
524
                );
525
                last MAINLOOP;
526
            }
527
            my $marcxml = '';
528
            if ($stage == 1) {
529
                $marc_sth->execute(($biblionumber)) || die('Query failed: ' . $dbh->errstr);
530
                my @marc_results = $marc_sth->fetchrow_array();
531
                if (@marc_results) {
532
                    $marcxml = $marc_results[0];
533
                }
534
            }
535
            my $oai_sets = GetOAISetsBiblio($biblionumber);
536
            my @setSpecs;
537
            foreach (@$oai_sets) {
538
                push @setSpecs, $_->{spec};
539
            }
540
            $self->record( C4::OAI::Record->new(
541
                $repository, $marcxml, $timestamp, \@setSpecs, $stage == 0 || !$marcxml,
542
                identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
543
                metadataPrefix  => $token->{metadata_prefix}
544
            ) );
493
        }
545
        }
494
        $self->record( C4::OAI::Record->new(
546
        # Reset offset for next stage
495
            $repository, $marcxml, $timestamp, \@setSpecs,
547
        $offset = 0;
496
            identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
497
            metadataPrefix  => $token->{metadata_prefix}
498
        ) );
499
        $pos++;
500
    }
548
    }
501
    $self->resumptionToken(
502
        new C4::OAI::ResumptionToken(
503
            metadataPrefix  => $token->{metadata_prefix},
504
            from            => $token->{from},
505
            until           => $token->{until},
506
            offset          => $pos,
507
            set             => $token->{set}
508
        )
509
    ) if ($pos > $token->{offset});
510
549
511
    return $self;
550
    return $self;
512
}
551
}
513
552
514
# __END__ C4::OAI::ListRecords
553
# __END__ C4::OAI::ListRecords
515
554
555
package C4::OAI::Utils;
556
557
use strict;
558
559
sub fix_date {
560
    my ($class, $date, $end) = @_;
561
562
    if (length($date) == 10) {
563
        if ($end) {
564
            $date .= ' 23:59:59';
565
        } else {
566
            $date .= ' 00:00:00';
567
        }
568
    } else {
569
        $date = substr($date, 0, 10) . ' ' . substr($date, 11, 8);
570
    }
571
    return $date;
572
}
573
574
sub build_list_sql {
575
    my ($class, $deleted, $set, $max, $offset) = @_;
576
577
    my $table = $deleted ? 'deletedbiblioitems' : 'biblioitems';
578
579
    my $sql = "
580
SELECT bi.biblionumber, bi.timestamp
581
  FROM $table bi
582
  WHERE bi.timestamp >= ? AND bi.timestamp <= ?
583
  ORDER BY bi.biblionumber
584
";
585
586
    # Use a subquery for sets since it allows us to use an index for the subquery in
587
    # biblioitems table and is quite a bit faster than a join.
588
    if (defined $set) {
589
        $sql = "
590
SELECT bi.* FROM ($sql) bi
591
  WHERE bi.biblionumber in (SELECT osb.biblionumber FROM oai_sets_biblios osb WHERE osb.set_id = ?)
592
";
593
    }
594
    $sql .= '
595
  LIMIT ' . ($max + 1) . "
596
  OFFSET $offset
597
";
598
599
    return $sql;
600
}
601
602
# __END__ C4::OAI::Utils;
603
516
604
517
605
518
package C4::OAI::Repository;
606
package C4::OAI::Repository;
Lines 541-547 sub new { Link Here
541
629
542
    $self->{ koha_identifier      } = C4::Context->preference("OAI-PMH:archiveID");
630
    $self->{ koha_identifier      } = C4::Context->preference("OAI-PMH:archiveID");
543
    $self->{ koha_max_count       } = C4::Context->preference("OAI-PMH:MaxCount");
631
    $self->{ koha_max_count       } = C4::Context->preference("OAI-PMH:MaxCount");
544
    $self->{ koha_metadata_format } = ['oai_dc', 'marcxml'];
632
    $self->{ koha_metadata_format } = ['oai_dc', 'marc21', 'marcxml'];
545
    $self->{ koha_stylesheet      } = { }; # Build when needed
633
    $self->{ koha_stylesheet      } = { }; # Build when needed
546
634
547
    # Load configuration file if defined in OAI-PMH:ConfFile syspref
635
    # Load configuration file if defined in OAI-PMH:ConfFile syspref
Lines 639-645 C4::OAI::Repository - Handles OAI-PMH requests for a Koha database. Link Here
639
This object extend HTTP::OAI::Repository object.
727
This object extend HTTP::OAI::Repository object.
640
It accepts OAI-PMH HTTP requests and returns result.
728
It accepts OAI-PMH HTTP requests and returns result.
641
729
642
This OAI-PMH server can operate in a simple mode and extended one. 
730
This OAI-PMH server can operate in a simple mode and extended one.
643
731
644
In simple mode, repository configuration comes entirely from Koha system
732
In simple mode, repository configuration comes entirely from Koha system
645
preferences (OAI-PMH:archiveID and OAI-PMH:MaxCount) and the server returns
733
preferences (OAI-PMH:archiveID and OAI-PMH:MaxCount) and the server returns
Lines 662-669 configuration file koha-oai.conf can look like that: Link Here
662
      metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs
750
      metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs
663
      schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd
751
      schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd
664
      xsl_file: /usr/local/koha/xslt/vs.xsl
752
      xsl_file: /usr/local/koha/xslt/vs.xsl
665
    marcxml:
753
    marc21 or marcxml:
666
      metadataPrefix: marxml
754
      metadataPrefix: marc21 or marxml
667
      metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
755
      metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
668
      schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
756
      schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
669
    oai_dc:
757
    oai_dc:
670
- 

Return to bug 15108