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

(-)a/C4/Biblio/Diff.pm (+325 lines)
Line 0 Link Here
1
package C4::Biblio::Diff;
2
3
# Copyright KohaSuomi 2016
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
use Scalar::Util qw(blessed);
22
use Try::Tiny;
23
24
use Koha::Exception::BadParameter;
25
26
=head SYNOPSIS
27
28
    Diff takes any amount of MARC::Records and produces a diff of all the MARC
29
    elements that are different in atleast one of the records.
30
31
=cut
32
33
=head new
34
35
    my $diff = C4::Biblio::Diff->new($params, @MARC::Records);
36
37
@PARAM1 HASHRef of options,
38
        'excludedFields' => ['999', '952', ...] #Which MARC::Fields to exclude from the comparison result
39
@PARAM2..n MARC::Record-objects to diff
40
@RETURNS C4::Biblio::Diff-object
41
42
=cut
43
44
sub new {
45
    my ($class, $params, @records) = @_;
46
    my $self = (ref($params) eq 'HASH') ? $params :  {};
47
    bless($self, $class);
48
49
    $self->{records} = [];
50
    foreach my $r (@records) {
51
        $self->addRecord($r);
52
    }
53
    if ($self->{excludedFields}) {
54
        $self->setExcludedFields( $self->{excludedFields} );
55
    }
56
57
    return $self;
58
}
59
60
=head addRecord
61
62
    $diff = $diff->addRecord($MARC::Record);
63
64
@PARAM1, MARC::Record-object.
65
@RETURNS C4::Biblio::Diff to chain commands
66
@THROWS Koha::Exception::BadParameter
67
68
=cut
69
70
sub addRecord {
71
    my ($self, $record) = @_;
72
73
    unless(blessed($record) && $record->isa('MARC::Record')) {
74
        my @cc = caller(0);
75
        Koha::Exception::BadParameter->throw(error => $cc[3]."()> Param \$record '$record' is not a MARC::Record-object");
76
    }
77
    push(@{$self->{records}}, $record);
78
    return $self;
79
}
80
81
sub getRecords {
82
    return shift->{records};
83
}
84
sub getExcludedFields {
85
    return shift->{excludedFields};
86
}
87
sub setExcludedFields {
88
    my ($self, $excludedFields) = @_;
89
    unless(ref($excludedFields) eq 'ARRAY') {
90
        my @cc1 = caller(1);
91
        Koha::Exception::BadParameter->throw(error => $cc1[3]." is trying to setExcludedFields, but the param \$excludedFields '$excludedFields' is not an ARRAYref.");
92
    }
93
    $self->{excludedFields} = {}; #Make a easy to search hash
94
    foreach my $f (@$excludedFields) {
95
        $self->{excludedFields}->{$f} = 1;
96
    }
97
    return $self;
98
}
99
sub isFieldExcluded {
100
    my ($self, $field) = @_;
101
    my $ef = $self->getExcludedFields();
102
    if ($ef && $ef->{$field}) {
103
        return 1;
104
    }
105
    return 0;
106
}
107
108
=head diffRecords
109
110
Generates a multitiered and parallel diff which lists all the changed MARC-(sub)fields
111
and indicators horizontally between any amount of given MARC::Records.
112
113
@PARAMS List of MARC::Records to be compared between each others for difference.
114
@RETURNS HASHmonster, depicting all the MARC elements where even one of the given MARC::Records differ from the others:
115
    {
116
        '001' => [
117
            '3243256',
118
            '10042',
119
            undef,
120
        ],
121
        '003' => [
122
            'VAARA',
123
            'LUMME',
124
            'KYYTI',
125
        ],
126
        '049' => [
127
            {
128
                '_i1' => [
129
                    ' ',
130
                    1,
131
                    undef,
132
                ],
133
                'a' => [
134
                    undef,
135
                    'K18',
136
                    undef,
137
                ],
138
                'b' => [
139
                    undef,
140
                    'YLE',
141
                    undef,
142
                ],
143
            },
144
        ],
145
        '245' => [
146
            {
147
                '_i2' => [
148
                    3,
149
                    undef,
150
                    1,
151
                ],
152
                'a' => [
153
                    'Rickshaw /',
154
                    'Rickshaw',
155
                    'Rickshaw',
156
                ],
157
            },
158
        ],
159
    }
160
161
=cut
162
163
sub diffRecords {
164
    my ($self) = @_;
165
    my $records = $self->getRecords();
166
    my %availableFields;
167
    my %fieldRepetitions;
168
    my %subfieldRepetitions;
169
170
    #collect all found fields and subfields to to a single stack.
171
    #Collect repetiton counts of fields and subfields.
172
    foreach my $r (@$records) {
173
        foreach my $f ($r->fields()) { #iterate fields
174
            next if $self->isFieldExcluded($f->tag());
175
            if ($f->is_control_field()) {
176
                $availableFields{$f->tag()} = 1;
177
            }
178
            else {
179
                $availableFields{$f->tag()} = {} unless($availableFields{$f->tag()});
180
                my $sfs = $availableFields{$f->tag()};
181
182
                my @fields = $r->field($f->tag());
183
                $fieldRepetitions{$f->tag()} = scalar(@fields) if(not($fieldRepetitions{$f->tag()}) || $fieldRepetitions{$f->tag()} < scalar(@fields));
184
185
                foreach my $sf ($f->subfields()) { #Iterate subfields
186
                    my @sfs = $f->subfield( $sf->[0] );
187
                    $subfieldRepetitions{ $f->tag().$sf->[0] } = scalar(@sfs) if(not($subfieldRepetitions{ $f->tag().$sf->[0] }) || $subfieldRepetitions{ $f->tag().$sf->[0] } < scalar(@sfs));
188
                    $sfs->{ $sf->[0] } = 1;
189
                }
190
            }
191
        }
192
    }
193
194
    my %diff;
195
    ##Iterate all found indicators, fields and subfields and diff between all given records
196
    #Remember that all fields and subfields can be repeated
197
    foreach my $fk (sort(keys(%availableFields))) { #Iterate fields
198
199
        if (int($fk) < 10) { #Control fields
200
            my @candidates;
201
            for(my $ri=0 ; $ri<scalar(@$records) ; $ri++) {
202
                my $r = $records->[$ri];
203
                my $field = $r->field($fk);
204
                $candidates[$ri] = ($field) ? $field->data() : undef;
205
            }
206
            if (_valuesDiff(\@candidates)) {
207
                $diff{$fk} = \@candidates;
208
            }
209
        } #EO control field
210
        else { #Data fields
211
            my @fs;
212
            for(my $fi=0   ;   $fi<$fieldRepetitions{$fk}   ;   $fi++) { #Iterate field repetitions
213
214
                foreach my $i (1..2) { #Diff indicators
215
                    my @candidates;
216
                    for(my $ri=0 ; $ri<scalar(@$records) ; $ri++) {
217
                        my $r = $records->[$ri];
218
                        $fs[$ri] = [$r->field($fk)] unless $fs[$ri];
219
220
                        $candidates[$ri] = ($fs[$ri]->[$fi]) ? $fs[$ri]->[$fi]->indicator($i) : undef;
221
                    }
222
                    if (_valuesDiff(\@candidates)) {
223
                        $diff{$fk} = [] unless $diff{$fk};
224
                        $diff{$fk}->[$fi] = {} unless $diff{$fk}->[$fi];
225
                        $diff{$fk}->[$fi]->{"_i$i"} = \@candidates;
226
                    }
227
                } #EO indicators
228
229
                foreach my $sfk (sort(keys(%{$availableFields{$fk}}))) { #Iterate subfields
230
                    my @sfs;
231
232
                    for(my $sfi=0   ;   $sfi<$subfieldRepetitions{$fk.$sfk}   ;   $sfi++) { #Iterate subfield repetitions
233
234
                        my @candidates;
235
                        for(my $ri=0 ; $ri<scalar(@$records) ; $ri++) {
236
                            my $r = $records->[$ri];
237
                            $fs[$ri] = [$r->field($fk)] unless $fs[$ri];
238
                            $sfs[$ri] = [$fs[$ri]->[$fi]->subfield($sfk)] if (not($sfs[$ri]) && $fs[$ri]->[$fi]);
239
240
                            $candidates[$ri] = ($sfs[$ri]) ? $sfs[$ri]->[$sfi] : undef;
241
                        }
242
                        if (_valuesDiff(\@candidates)) {
243
                            $diff{$fk} = [] unless $diff{$fk};
244
                            $diff{$fk}->[$fi] = {} unless $diff{$fk}->[$fi];
245
                            $diff{$fk}->[$fi]->{$sfk} = [] unless $diff{$fk}->[$fi]->{$sfk};
246
                            $diff{$fk}->[$fi]->{$sfk}->[$sfi] = \@candidates;
247
                        }
248
                    } #EO subfield repetiton iterator
249
                } #EO subfields iterator
250
            } #EO Field repetiton iterator
251
        } #EO Data fields
252
    } #EO fields iterator
253
254
##DEBUG DEBUG Find out why some diffs have a undefined array index and defined array indexes after that?
255
sub throwUp {
256
    my ($records, $diff, $msg) = @_;
257
    require Data::Dumper::Dumper;
258
    die "\n$msg\n\n@$records\n\n".Data::Dumper::Dumper($diff)."\n\n";
259
}
260
foreach my $fk (sort(keys(%availableFields))) { #Iterate fields
261
    if (int($fk) < 10) { #Control fields
262
        if (exists($diff{$fk}) && not($diff{$fk})) {
263
            throwUp($records, \%diff, "Control field null");
264
        }
265
    } #EO control field
266
    else { #Data fields
267
        if (exists($diff{$fk}) && not($diff{$fk})) {
268
            throwUp($records, \%diff, "Data field null");
269
        }
270
        for(my $fi=0   ;   $fi<$fieldRepetitions{$fk}   ;   $fi++) { #Iterate field repetitions
271
            foreach my $i (1..2) { #Diff indicators
272
                if (exists($diff{$fk}->[$fi]) && not($diff{$fk}->[$fi])) {
273
                    throwUp($records, \%diff, "Data field repetition null");
274
                }
275
            } #EO indicators
276
            foreach my $sfk (sort(keys(%{$availableFields{$fk}}))) { #Iterate subfields
277
                if (exists($diff{$fk}->[$fi]->{$sfk}) && not($diff{$fk}->[$fi]->{$sfk})) {
278
                    throwUp($records, \%diff, "Subfield null");
279
                }
280
                for(my $sfi=0   ;   $sfi<$subfieldRepetitions{$fk.$sfk}   ;   $sfi++) { #Iterate subfield repetitions
281
                    if (exists($diff{$fk}->[$fi]->{$sfk}->[$sfi]) && not($diff{$fk}->[$fi]->{$sfk}->[$sfi])) {
282
                        throwUp($records, \%diff, "Subfield repetition null");
283
                    }
284
                } #EO subfield repetiton iterator
285
            } #EO subfields iterator
286
        } #EO Field repetiton iterator
287
    } #EO Data fields
288
} #EO fields iterator
289
##EO DEBUG DEBUG
290
291
    $self->{diff} = \%diff;
292
    return $self->{diff};
293
}
294
295
=head _valuesDiff
296
297
    if ($diff->_valuesDiff($candidates)) {
298
        #Candidates do not match
299
    }
300
    else {
301
        #All candidates match
302
    }
303
304
TODO::This is a good point to change the similarity logic of this diff:ing tool if necessary.
305
306
@PARAM1 ARRAYref of Scalar-values, these are compared for similarity.
307
@RETURNS Boolean, true if values differ
308
                  false if they are the same
309
=cut
310
311
sub _valuesDiff {
312
    my ($candidates) = @_;
313
    for(my $i=1 ; $i<scalar(@$candidates) ; $i++) {
314
        #Normalize values for comparison
315
        my $prevValue = defined($candidates->[$i-1]) ? $candidates->[$i-1] : '';
316
        my $nextValue = defined($candidates->[$i])   ? $candidates->[$i] : '';
317
318
        if ($prevValue ne $nextValue) {
319
            return 1;
320
        }
321
    }
322
    return 0;
323
}
324
325
1;
(-)a/t/db_dependent/Biblio/Diff/diff.t (+217 lines)
Line 0 Link Here
1
# Copyright 2016 KohaSuomi
2
#
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
use Test::More;
20
use Scalar::Util qw(blessed);
21
use Try::Tiny;
22
23
use C4::Biblio::Diff;
24
25
use t::db_dependent::Biblio::Diff::localRecords;
26
use t::lib::TestObjects::BiblioFactory;
27
28
29
subtest "C4::Biblio::Diff", \&biblioDiff;
30
sub biblioDiff {
31
    my $testContext = {};
32
    eval {
33
        my $records = t::db_dependent::Biblio::Diff::localRecords::create($testContext);
34
        my @recKeys = sort(keys(%$records));
35
36
        my $diff = C4::Biblio::Diff->new(
37
                        {excludedFields => ['999', '942', '952']},
38
                        $records->{ $recKeys[0] },
39
                        $records->{ $recKeys[1] },
40
                        $records->{ $recKeys[2] },
41
                    );
42
        my $d = $diff->diffRecords();
43
44
        my $expectedDiff = {
45
            '001' => [
46
                '300841',
47
                '21937',
48
                '4312727',
49
            ],
50
            '003' => [
51
                'KYYTI',
52
                'OUTI',
53
                undef,
54
            ],
55
            '007' => [
56
                undef,
57
                undef,
58
                'ta',
59
            ],
60
            '020' => [
61
                {
62
                    'a' => [
63
                        [
64
                            '9510108303',
65
                            '9510108304',
66
                            '9510108305',
67
                        ],
68
                    ],
69
                    'q' => [
70
                        [
71
                            'NID.',
72
                            'NID.',
73
                            undef,
74
                        ],
75
                    ],
76
                    'c' => [
77
                        [
78
                            undef,
79
                            '7.74 EUR',
80
                            undef,
81
                        ],
82
                    ],
83
                },
84
            ],
85
            '041' => [
86
                {
87
                    '_i1' => [
88
                        undef,
89
                        undef,
90
                        '0',
91
                    ],
92
                    '_i2' => [
93
                        undef,
94
                        undef,
95
                        ' '
96
                    ],
97
                    'a' => [
98
                        [
99
                            undef,
100
                            undef,
101
                            'lat',
102
                        ],
103
                        [
104
                            undef,
105
                            undef,
106
                            'swe',
107
                        ],
108
                        [
109
                            undef,
110
                            undef,
111
                            'eng',
112
                        ],
113
                    ],
114
                },
115
            ],
116
            '245' => [
117
                {
118
                    '_i2' => [
119
                        '4',
120
                        '0',
121
                        '0',
122
                    ],
123
                    'a' => [
124
                        [
125
                            'THE WISHING TREE /',
126
                            'TYRANNIT VOIVAT PAREMMIN :',
127
                            'TYRANNIT VOIVAT PAREMMIN :',
128
                        ],
129
                    ],
130
                    'b' => [
131
                        [
132
                            undef,
133
                            'RUNOJA /',
134
                            'RUNOJA /',
135
                        ],
136
                    ],
137
                    'c' => [
138
                        [
139
                            'USHA BAHL.',
140
                            'AKI LUOSTARINEN.',
141
                            'AKI LUOSTARINEN.',
142
                        ],
143
                    ],
144
                },
145
                {
146
                    '_i1' => [
147
                        undef,
148
                        '0',
149
                        undef,
150
                    ],
151
                    '_i2' => [
152
                        undef,
153
                        '0',
154
                        undef,
155
                    ],
156
                    'a' => [
157
                        [
158
                            undef,
159
                            'TYRANNIT VOIVAT PARHAITEN :',
160
                            undef,
161
                        ],
162
                    ],
163
                },
164
            ],
165
        };
166
167
        is_deeply($d, $expectedDiff, "Deep diff is as expected");
168
169
170
        $diff = C4::Biblio::Diff->new(
171
                        {},
172
                        $records->{ $recKeys[1] },
173
                        $records->{ $recKeys[2] },
174
                        $records->{ $recKeys[0] },
175
                    );
176
        $d = $diff->diffRecords();
177
        is($d->{'003'}->[0],
178
           'OUTI',
179
           "Same diff, different order");
180
        is($d->{'003'}->[1],
181
           undef,
182
           "Same diff, different order");
183
        is($d->{'003'}->[2],
184
           'KYYTI',
185
           "Same diff, different order");
186
    };
187
    if ($@) {
188
        ok(0, $@);
189
    }
190
    t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext);
191
}
192
193
subtest "C4::Biblio::Diff undef indexes", \&biblioDiffUndefIndexes;
194
sub biblioDiffUndefIndexes {
195
    my $testContext = {};
196
    eval {
197
        my $records = t::db_dependent::Biblio::Diff::localRecords::create2($testContext);
198
        my @recKeys = sort(keys(%$records));
199
200
        my $diff = C4::Biblio::Diff->new(
201
                        {excludedFields => ['999', '942', '952']},
202
                        $records->{ $recKeys[0] },
203
                        $records->{ $recKeys[1] },
204
                    );
205
        my $d = $diff->diffRecords();
206
207
        my $expectedDiff = {};
208
209
        is_deeply($d, $expectedDiff, "Deep diff is as expected");
210
    };
211
    if ($@) {
212
        ok(0, $@);
213
    }
214
    t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext);
215
}
216
217
done_testing();
(-)a/t/db_dependent/Biblio/Diff/localRecords.pm (-1 / +455 lines)
Line 0 Link Here
0
- 
1
package t::db_dependent::Biblio::Diff::localRecords;
2
#
3
# Copyright 2016 KohaSuomi
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use t::lib::TestObjects::BiblioFactory;
23
24
=head IN THIS FILE
25
26
Here we create some fully catalogued records to play with
27
28
=cut
29
30
sub create {
31
my ($testContext) = @_;
32
my ($record, @records);
33
34
$record = <<RECORD;
35
<record>
36
  <leader>00510cam a22002054a 4500</leader>
37
  <controlfield tag="001">300841</controlfield>
38
  <controlfield tag="003">KYYTI</controlfield>
39
  <datafield tag="020" ind1=" " ind2=" ">
40
    <subfield code="a">9510108303</subfield>
41
    <subfield code="q">NID.</subfield>
42
  </datafield>
43
  <datafield tag="245" ind1="1" ind2="4">
44
    <subfield code="a">THE WISHING TREE /</subfield>
45
    <subfield code="c">USHA BAHL.</subfield>
46
  </datafield>
47
  <datafield tag="942" ind1=" " ind2=" ">
48
    <subfield code="c">BK</subfield>
49
    <subfield code="1">1996-05-01 00:00:00</subfield>
50
  </datafield>
51
</record>
52
RECORD
53
push(@records, {record => $record});
54
55
$record = <<RECORD;
56
<record>
57
  <leader>00618cam a22002294a 4500</leader>
58
  <controlfield tag="001">21937</controlfield>
59
  <controlfield tag="003">OUTI</controlfield>
60
  <datafield tag="020" ind1=" " ind2=" ">
61
    <subfield code="a">9510108304</subfield>
62
    <subfield code="q">NID.</subfield>
63
    <subfield code="c">7.74 EUR</subfield>
64
  </datafield>
65
  <datafield tag="245" ind1="1" ind2="0">
66
    <subfield code="a">TYRANNIT VOIVAT PAREMMIN :</subfield>
67
    <subfield code="b">RUNOJA /</subfield>
68
    <subfield code="c">AKI LUOSTARINEN.</subfield>
69
  </datafield>
70
  <datafield tag="245" ind1="0" ind2="0">
71
    <subfield code="a">TYRANNIT VOIVAT PARHAITEN :</subfield>
72
  </datafield>
73
</record>
74
RECORD
75
push(@records, {record => $record});
76
77
$record = <<RECORD;
78
<record>
79
  <leader>01096cam a22003134i 4500</leader>
80
  <controlfield tag="001">4312727</controlfield>
81
  <controlfield tag="007">ta</controlfield>
82
  <datafield tag="020" ind1=" " ind2=" ">
83
    <subfield code="a">9510108305</subfield>
84
  </datafield>
85
  <datafield tag="041" ind1="0" ind2=" ">
86
    <subfield code="a">lat</subfield>
87
    <subfield code="a">swe</subfield>
88
    <subfield code="a">eng</subfield>
89
  </datafield>
90
  <datafield tag="245" ind1="1" ind2="0">
91
    <subfield code="a">TYRANNIT VOIVAT PAREMMIN :</subfield>
92
    <subfield code="b">RUNOJA /</subfield>
93
    <subfield code="c">AKI LUOSTARINEN.</subfield>
94
  </datafield>
95
  <datafield tag="952" ind1="1" ind2="0">
96
    <subfield code="a">CPL</subfield>
97
    <subfield code="b">CPL</subfield>
98
  </datafield>
99
  <datafield tag="942" ind1=" " ind2=" ">
100
    <subfield code="2">ykl</subfield>
101
    <subfield code="c">BK</subfield>
102
    <subfield code="1">2016-02-15T15:03:05</subfield>
103
  </datafield>
104
</record>
105
RECORD
106
push(@records, {record => $record});
107
108
return t::lib::TestObjects::BiblioFactory->createTestGroup(\@records, undef, $testContext);
109
}
110
111
sub create2 {
112
my ($testContext) = @_;
113
my ($record, @records);
114
115
$record = <<RECORD;
116
<record
117
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
118
    xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
119
    xmlns="http://www.loc.gov/MARC21/slim">
120
121
  <leader>01708cam a2200553zi 4500</leader>
122
  <controlfield tag="001">000035144</controlfield>
123
  <controlfield tag="003">FI-MELINDA</controlfield>
124
  <controlfield tag="005">20160304181804.0</controlfield>
125
  <controlfield tag="008">820317s1981    fi |||||||||||||||f|fin||</controlfield>
126
  <datafield tag="015" ind1=" " ind2=" ">
127
    <subfield code="a">fx37918</subfield>
128
    <subfield code="2">skl</subfield>
129
  </datafield>
130
  <datafield tag="020" ind1=" " ind2=" ">
131
    <subfield code="a">9510107417</subfield>
132
    <subfield code="c">43,80 mk</subfield>
133
    <subfield code="q">sid.</subfield>
134
  </datafield>
135
  <datafield tag="035" ind1=" " ind2=" ">
136
    <subfield code="a">(FI-MELINDA)000035144</subfield>
137
  </datafield>
138
  <datafield tag="035" ind1=" " ind2=" ">
139
    <subfield code="a">(FI-MELINDA)000035144</subfield>
140
  </datafield>
141
  <datafield tag="040" ind1=" " ind2=" ">
142
    <subfield code="a">FI-NL</subfield>
143
  </datafield>
144
  <datafield tag="041" ind1="1" ind2=" ">
145
    <subfield code="a">fin</subfield>
146
    <subfield code="h">eng</subfield>
147
  </datafield>
148
  <datafield tag="042" ind1=" " ind2=" ">
149
    <subfield code="a">finb</subfield>
150
  </datafield>
151
  <datafield tag="072" ind1=" " ind2="7">
152
    <subfield code="a">87</subfield>
153
    <subfield code="2">kkaa</subfield>
154
  </datafield>
155
  <datafield tag="080" ind1=" " ind2=" ">
156
    <subfield code="a">820</subfield>
157
    <subfield code="x">-3</subfield>
158
  </datafield>
159
  <datafield tag="082" ind1="1" ind2="4">
160
    <subfield code="a">84.5</subfield>
161
  </datafield>
162
  <datafield tag="084" ind1=" " ind2=" ">
163
    <subfield code="a">84.2</subfield>
164
    <subfield code="2">ykl</subfield>
165
  </datafield>
166
  <datafield tag="084" ind1=" " ind2=" ">
167
    <subfield code="a">84.2</subfield>
168
    <subfield code="2">ykl</subfield>
169
  </datafield>
170
  <datafield tag="084" ind1=" " ind2=" ">
171
    <subfield code="a">84.5</subfield>
172
    <subfield code="2">ykl</subfield>
173
  </datafield>
174
  <datafield tag="100" ind1="1" ind2=" ">
175
    <subfield code="a">Doyle, Arthur Conan.</subfield>
176
  </datafield>
177
  <datafield tag="240" ind1="1" ind2="4">
178
    <subfield code="a">The hound of the Baskervilles</subfield>
179
  </datafield>
180
  <datafield tag="245" ind1="1" ind2="0">
181
    <subfield code="a">Baskervillen koira /</subfield>
182
    <subfield code="c">Arthur Conan Doyle.</subfield>
183
  </datafield>
184
  <datafield tag="250" ind1=" " ind2=" ">
185
    <subfield code="a">10. p.</subfield>
186
  </datafield>
187
  <datafield tag="260" ind1=" " ind2=" ">
188
    <subfield code="a">Porvoo ;</subfield>
189
    <subfield code="a">Hki ;</subfield>
190
    <subfield code="a">Juva :</subfield>
191
    <subfield code="b">WSOY,</subfield>
192
    <subfield code="c">1981</subfield>
193
    <subfield code="e">(Porvoo)</subfield>
194
  </datafield>
195
  <datafield tag="300" ind1=" " ind2=" ">
196
    <subfield code="a">159, [1] s. ;</subfield>
197
    <subfield code="c">21 cm.</subfield>
198
  </datafield>
199
  <datafield tag="336" ind1=" " ind2=" ">
200
    <subfield code="a">Teksti</subfield>
201
  </datafield>
202
  <datafield tag="337" ind1=" " ind2=" ">
203
    <subfield code="a">ei välittävää laitetta</subfield>
204
  </datafield>
205
  <datafield tag="490" ind1="1" ind2=" ">
206
    <subfield code="a">Koulun peruskirjasto;</subfield>
207
    <subfield code="v">22</subfield>
208
  </datafield>
209
  <datafield tag="500" ind1=" " ind2=" ">
210
    <subfield code="a">9. p. 1972.</subfield>
211
  </datafield>
212
  <datafield tag="500" ind1=" " ind2=" ">
213
    <subfield code="a">Korjattu suomennos.</subfield>
214
  </datafield>
215
  <datafield tag="500" ind1=" " ind2=" ">
216
    <subfield code="a">Lisäpainokset: 11. p. 1983.</subfield>
217
  </datafield>
218
  <datafield tag="500" ind1=" " ind2=" ">
219
    <subfield code="a">Luokkataso: yläkoulu.</subfield>
220
  </datafield>
221
  <datafield tag="500" ind1=" " ind2=" ">
222
    <subfield code="a">9. P. 1972. - KORJATTU SUOMENNOS</subfield>
223
  </datafield>
224
  <datafield tag="500" ind1=" " ind2=" ">
225
    <subfield code="a">11. P. 1983</subfield>
226
  </datafield>
227
  <datafield tag="600" ind1="1" ind2="4">
228
    <subfield code="a">Holmes, Sherlock,</subfield>
229
    <subfield code="c">fikt.</subfield>
230
  </datafield>
231
  <datafield tag="600" ind1="1" ind2="4">
232
    <subfield code="a">Watson, John H.,</subfield>
233
    <subfield code="c">fikt.</subfield>
234
  </datafield>
235
  <datafield tag="650" ind1=" " ind2="7">
236
    <subfield code="a">kaunokirjallisuus</subfield>
237
    <subfield code="x">suomenkielinen kirjallisuus</subfield>
238
    <subfield code="2">ysa</subfield>
239
  </datafield>
240
  <datafield tag="650" ind1=" " ind2="7">
241
    <subfield code="a">kriminalfiktion</subfield>
242
    <subfield code="x">översättningar</subfield>
243
    <subfield code="x">finska</subfield>
244
    <subfield code="x">engelska</subfield>
245
    <subfield code="2">bella</subfield>
246
  </datafield>
247
  <datafield tag="650" ind1=" " ind2="7">
248
    <subfield code="a">rikoskirjallisuus</subfield>
249
    <subfield code="x">käännökset</subfield>
250
    <subfield code="x">suomen kieli</subfield>
251
    <subfield code="x">englannin kieli</subfield>
252
    <subfield code="2">kaunokki</subfield>
253
  </datafield>
254
  <datafield tag="650" ind1=" " ind2="7">
255
    <subfield code="a">salapoliisikirjallisuus</subfield>
256
    <subfield code="z">Englanti</subfield>
257
    <subfield code="y">1800-luku</subfield>
258
    <subfield code="2">kaunokki</subfield>
259
  </datafield>
260
  <datafield tag="650" ind1=" " ind2="7">
261
    <subfield code="a">aateli</subfield>
262
    <subfield code="2">kaunokki</subfield>
263
  </datafield>
264
  <datafield tag="650" ind1=" " ind2="7">
265
    <subfield code="a">maaseutu</subfield>
266
    <subfield code="2">kaunokki</subfield>
267
  </datafield>
268
  <datafield tag="650" ind1=" " ind2="7">
269
    <subfield code="a">kummittelu</subfield>
270
    <subfield code="2">kaunokki</subfield>
271
  </datafield>
272
  <datafield tag="830" ind1=" " ind2="0">
273
    <subfield code="a">Koulun peruskirjasto;</subfield>
274
    <subfield code="v">22</subfield>
275
  </datafield>
276
  <datafield tag="942" ind1=" " ind2=" ">
277
    <subfield code="2">ykl</subfield>
278
    <subfield code="c">BR</subfield>
279
  </datafield>
280
</record>
281
RECORD
282
push(@records, {record => $record});
283
284
$record = <<RECORD;
285
<record
286
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
287
    xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
288
    xmlns="http://www.loc.gov/MARC21/slim">
289
290
  <leader>01708cam a2200553zi 4500</leader>
291
  <controlfield tag="001">000035144</controlfield>
292
  <controlfield tag="003">FI-MELINDA</controlfield>
293
  <controlfield tag="005">20160304181804.0</controlfield>
294
  <controlfield tag="008">820317s1981    fi |||||||||||||||f|fin||</controlfield>
295
  <datafield tag="015" ind1=" " ind2=" ">
296
    <subfield code="a">fx37918</subfield>
297
    <subfield code="2">skl</subfield>
298
  </datafield>
299
  <datafield tag="020" ind1=" " ind2=" ">
300
    <subfield code="a">9510107418</subfield>
301
    <subfield code="c">43,80 mk</subfield>
302
    <subfield code="q">sid.</subfield>
303
  </datafield>
304
  <datafield tag="035" ind1=" " ind2=" ">
305
    <subfield code="a">(FI-MELINDA)000035144</subfield>
306
  </datafield>
307
  <datafield tag="035" ind1=" " ind2=" ">
308
    <subfield code="a">(FI-MELINDA)000035144</subfield>
309
  </datafield>
310
  <datafield tag="040" ind1=" " ind2=" ">
311
    <subfield code="a">FI-NL</subfield>
312
  </datafield>
313
  <datafield tag="041" ind1="1" ind2=" ">
314
    <subfield code="a">fin</subfield>
315
    <subfield code="h">eng</subfield>
316
  </datafield>
317
  <datafield tag="042" ind1=" " ind2=" ">
318
    <subfield code="a">finb</subfield>
319
  </datafield>
320
  <datafield tag="072" ind1=" " ind2="7">
321
    <subfield code="a">87</subfield>
322
    <subfield code="2">kkaa</subfield>
323
  </datafield>
324
  <datafield tag="080" ind1=" " ind2=" ">
325
    <subfield code="a">820</subfield>
326
    <subfield code="x">-3</subfield>
327
  </datafield>
328
  <datafield tag="082" ind1="1" ind2="4">
329
    <subfield code="a">84.5</subfield>
330
  </datafield>
331
  <datafield tag="084" ind1=" " ind2=" ">
332
    <subfield code="a">84.2</subfield>
333
    <subfield code="2">ykl</subfield>
334
  </datafield>
335
  <datafield tag="084" ind1=" " ind2=" ">
336
    <subfield code="a">84.2</subfield>
337
    <subfield code="2">ykl</subfield>
338
  </datafield>
339
  <datafield tag="084" ind1=" " ind2=" ">
340
    <subfield code="a">84.5</subfield>
341
    <subfield code="2">ykl</subfield>
342
  </datafield>
343
  <datafield tag="100" ind1="1" ind2=" ">
344
    <subfield code="a">Doyle, Arthur Conan.</subfield>
345
  </datafield>
346
  <datafield tag="240" ind1="1" ind2="4">
347
    <subfield code="a">The hound of the Baskervilles</subfield>
348
  </datafield>
349
  <datafield tag="245" ind1="1" ind2="0">
350
    <subfield code="a">Baskervillen koira /</subfield>
351
    <subfield code="c">Arthur Conan Doyle.</subfield>
352
  </datafield>
353
  <datafield tag="250" ind1=" " ind2=" ">
354
    <subfield code="a">10. p.</subfield>
355
  </datafield>
356
  <datafield tag="260" ind1=" " ind2=" ">
357
    <subfield code="a">Porvoo ;</subfield>
358
    <subfield code="a">Hki ;</subfield>
359
    <subfield code="a">Juva :</subfield>
360
    <subfield code="b">WSOY,</subfield>
361
    <subfield code="c">1981</subfield>
362
    <subfield code="e">(Porvoo)</subfield>
363
  </datafield>
364
  <datafield tag="300" ind1=" " ind2=" ">
365
    <subfield code="a">159, [1] s. ;</subfield>
366
    <subfield code="c">21 cm.</subfield>
367
  </datafield>
368
  <datafield tag="336" ind1=" " ind2=" ">
369
    <subfield code="a">Teksti</subfield>
370
  </datafield>
371
  <datafield tag="337" ind1=" " ind2=" ">
372
    <subfield code="a">ei välittävää laitetta</subfield>
373
  </datafield>
374
  <datafield tag="490" ind1="1" ind2=" ">
375
    <subfield code="a">Koulun peruskirjasto;</subfield>
376
    <subfield code="v">22</subfield>
377
  </datafield>
378
  <datafield tag="500" ind1=" " ind2=" ">
379
    <subfield code="a">9. p. 1972.</subfield>
380
  </datafield>
381
  <datafield tag="500" ind1=" " ind2=" ">
382
    <subfield code="a">Korjattu suomennos.</subfield>
383
  </datafield>
384
  <datafield tag="500" ind1=" " ind2=" ">
385
    <subfield code="a">Lisäpainokset: 11. p. 1983.</subfield>
386
  </datafield>
387
  <datafield tag="500" ind1=" " ind2=" ">
388
    <subfield code="a">Luokkataso: yläkoulu.</subfield>
389
  </datafield>
390
  <datafield tag="500" ind1=" " ind2=" ">
391
    <subfield code="a">9. P. 1972. - KORJATTU SUOMENNOS</subfield>
392
  </datafield>
393
  <datafield tag="500" ind1=" " ind2=" ">
394
    <subfield code="a">11. P. 1983</subfield>
395
  </datafield>
396
  <datafield tag="600" ind1="1" ind2="4">
397
    <subfield code="a">Holmes, Sherlock,</subfield>
398
    <subfield code="c">fikt.</subfield>
399
  </datafield>
400
  <datafield tag="600" ind1="1" ind2="4">
401
    <subfield code="a">Watson, John H.,</subfield>
402
    <subfield code="c">fikt.</subfield>
403
  </datafield>
404
  <datafield tag="650" ind1=" " ind2="7">
405
    <subfield code="a">kaunokirjallisuus</subfield>
406
    <subfield code="x">suomenkielinen kirjallisuus</subfield>
407
    <subfield code="2">ysa</subfield>
408
  </datafield>
409
  <datafield tag="650" ind1=" " ind2="7">
410
    <subfield code="a">kriminalfiktion</subfield>
411
    <subfield code="x">översättningar</subfield>
412
    <subfield code="x">finska</subfield>
413
    <subfield code="x">engelska</subfield>
414
    <subfield code="2">bella</subfield>
415
  </datafield>
416
  <datafield tag="650" ind1=" " ind2="7">
417
    <subfield code="a">rikoskirjallisuus</subfield>
418
    <subfield code="x">käännökset</subfield>
419
    <subfield code="x">suomen kieli</subfield>
420
    <subfield code="x">englannin kieli</subfield>
421
    <subfield code="2">kaunokki</subfield>
422
  </datafield>
423
  <datafield tag="650" ind1=" " ind2="7">
424
    <subfield code="a">salapoliisikirjallisuus</subfield>
425
    <subfield code="z">Englanti</subfield>
426
    <subfield code="y">1800-luku</subfield>
427
    <subfield code="2">kaunokki</subfield>
428
  </datafield>
429
  <datafield tag="650" ind1=" " ind2="7">
430
    <subfield code="a">aateli</subfield>
431
    <subfield code="2">kaunokki</subfield>
432
  </datafield>
433
  <datafield tag="650" ind1=" " ind2="7">
434
    <subfield code="a">maaseutu</subfield>
435
    <subfield code="2">kaunokki</subfield>
436
  </datafield>
437
  <datafield tag="650" ind1=" " ind2="7">
438
    <subfield code="a">kummittelu</subfield>
439
    <subfield code="2">kaunokki</subfield>
440
  </datafield>
441
  <datafield tag="830" ind1=" " ind2="0">
442
    <subfield code="a">Koulun peruskirjasto;</subfield>
443
    <subfield code="v">22</subfield>
444
  </datafield>
445
  <datafield tag="942" ind1=" " ind2=" ">
446
    <subfield code="2">ykl</subfield>
447
    <subfield code="c">BR</subfield>
448
  </datafield>
449
</record>
450
RECORD
451
push(@records, {record => $record});
452
453
return t::lib::TestObjects::BiblioFactory->createTestGroup(\@records, undef, $testContext);
454
}
455
1;

Return to bug 15996