@natlibfi/marc-record-validators-melinda 9.0.4 → 9.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/isbn-issn.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * MARC record validators used in Melinda
6
6
  *
7
- * Copyright (c) 2014-2020 University Of Helsinki (The National Library Of Finland)
7
+ * Copyright (c) 2014-2022 University Of Helsinki (The National Library Of Finland)
8
8
  *
9
9
  * This file is part of marc-record-validators-melinda
10
10
  *
@@ -29,60 +29,115 @@
29
29
  import ISBN from 'isbn3';
30
30
  import validateISSN from '@natlibfi/issn-verify';
31
31
 
32
+ // handleInvalid: move invalid 020$a to 020$z, and invalid 022$a to 022$y
32
33
  export default ({hyphenateISBN = false, handleInvalid = false} = {}) => {
33
34
  return {
34
35
  validate, fix,
35
36
  description: 'Validates ISBN and ISSN values'
36
37
  };
37
38
 
38
- function getInvalidFields(record) {
39
- return record.get(/^(020|022)$/u).filter(field => { // eslint-disable-line prefer-named-capture-group
40
- // Check ISBN:
41
- if (field.tag === '020') {
42
- if (invalidField020(field)) {
43
- return true;
44
- }
39
+ function stringHasSpace(str) {
40
+ return str.indexOf(' ') > -1;
41
+ }
45
42
 
46
- const subfield = field.subfields.find(sf => sf.code === 'a');
47
- const auditedIsbn = ISBN.audit(subfield.value);
48
- if (!auditedIsbn.validIsbn) {
49
- return true;
50
- }
51
- // Should we refactor code by adding a function that returns legal set of values,
52
- // and then we compare subfield.value against that list?
53
- const parsedIsbn = ISBN.parse(subfield.value);
54
- if (hyphenateISBN) {
55
- return !(subfield.value === parsedIsbn.isbn10h || subfield.value === parsedIsbn.isbn13h);
56
- }
43
+ function trimSpaces(value) {
44
+ return value.replace(/^\s+/u, '').replace(/\s+$/u, '').replace(/\s+/gu, ' ');
45
+ }
57
46
 
58
- return !(subfield.value === parsedIsbn.isbn10 || subfield.value === parsedIsbn.isbn13);
59
- }
60
- // Check ISSN:
61
- if (invalidField022(field)) {
62
- return true;
63
- }
47
+ function isMultiWord(inputString) {
48
+ const trimmedString = trimSpaces(inputString);
49
+ return stringHasSpace(trimmedString);
50
+ }
64
51
 
65
- const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');
52
+ function getFirstWord(inputString) {
53
+ const trimmedString = trimSpaces(inputString);
54
+ const arr = trimmedString.split(' ');
55
+ return arr[0];
56
+ }
66
57
 
67
- return !validateISSN(subfield.value);
68
- });
58
+ function invalidISBN(isbn) {
59
+ const isbnOnly = getFirstWord(isbn);
60
+ const auditedIsbn = ISBN.audit(isbnOnly);
61
+ return !auditedIsbn.validIsbn;
62
+ }
69
63
 
70
- function invalidField020(field) {
71
- const subfieldA = field.subfields.find(sf => sf.code === 'a');
64
+ function invalidSubfield(subfield) {
65
+ if (subfield.code !== 'a') {
66
+ return false;
67
+ }
68
+ return invalidISBN(subfield.value) || isMultiWord(subfield.value);
69
+ }
72
70
 
73
- if (subfieldA === undefined) {
74
- const subfieldZ = field.subfields.find(sf => sf.code === 'z');
75
- if (subfieldZ !== undefined) {
76
- return false;
71
+
72
+ function invalidField020(field) {
73
+ if (field.subfields && field.subfields.some(sf => invalidSubfield(sf))) {
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+
79
+ function subfieldsIsbnRequiresHyphenation(subfield) {
80
+ if (!hyphenateISBN || !['a', 'z'].includes(subfield.code)) {
81
+ return false;
82
+ }
83
+
84
+ const isbn = getFirstWord(subfield.value);
85
+ if (subfield.code === 'a') {
86
+ return requiresHyphenation(isbn);
87
+ }
88
+
89
+ // $z is a bit hacky: hyphenation is required only iff valid and no '-' chars
90
+ if (isbn.indexOf('-') > -1) {
91
+ return false;
92
+ }
93
+ return !invalidISBN(isbn);
94
+
95
+ function requiresHyphenation(isbn) {
96
+ if (!hyphenateISBN) {
97
+ return false;
98
+ }
99
+ // Handle old notation such as "978-952-396-001-5 (nid.)"
100
+ const isbn2 = getFirstWord(isbn);
101
+
102
+ if (invalidISBN(isbn2)) {
103
+ return false;
104
+ }
105
+
106
+ const parsedIsbn = ISBN.parse(isbn2);
107
+ // Return true only if existing ISBN is a valid and hyphenated 10 or 13 digit ISBN:
108
+ return !(isbn2 === parsedIsbn.isbn10h || isbn2 === parsedIsbn.isbn13h);
109
+ }
110
+ }
111
+
112
+ function getRelevantFields(record) {
113
+ //return record.get(/^(?:020|022)$/u).filter(field => {
114
+ return record.fields.filter(field => {
115
+ if (!field.subfields) {
116
+ return false;
117
+ }
118
+ // Check ISBN:
119
+ if (field.tag === '020') {
120
+ if (invalidField020(field)) { // checks multiwordness
121
+ return true;
77
122
  }
78
- return true;
123
+ return fieldsIsbnRequiresHyphenation(field);
79
124
  }
80
125
 
81
- // If value contains space, it's not ok (it's typically something like "1234567890 (nid.)")
82
- if (subfieldA.value.indexOf(' ') > -1) {
83
- return true;
126
+ // Check ISSN:
127
+ if (field.tag === '022') {
128
+ if (invalidField022(field)) {
129
+ return true;
130
+ }
131
+
132
+ const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');
133
+
134
+ return !validateISSN(subfield.value);
84
135
  }
85
136
  return false;
137
+ });
138
+
139
+ function fieldsIsbnRequiresHyphenation(field) {
140
+ return field.subfields && field.subfields.some(sf => subfieldsIsbnRequiresHyphenation(sf));
86
141
  }
87
142
 
88
143
  function invalidField022(field) {
@@ -101,7 +156,7 @@ export default ({hyphenateISBN = false, handleInvalid = false} = {}) => {
101
156
  }
102
157
 
103
158
  function validate(record) {
104
- const fields = getInvalidFields(record);
159
+ const fields = getRelevantFields(record);
105
160
 
106
161
  if (fields.length === 0) {
107
162
  return {valid: true};
@@ -114,6 +169,10 @@ export default ({hyphenateISBN = false, handleInvalid = false} = {}) => {
114
169
  if (subfieldA) {
115
170
  return {name: 'ISBN', value: subfieldA.value};
116
171
  }
172
+ const subfieldZ = field.subfields.find(sf => sf.code === 'z');
173
+ if (subfieldZ) {
174
+ return {name: 'ISBN (subfield Z)', value: subfieldZ.value};
175
+ }
117
176
 
118
177
  return {name: 'ISBN', value: undefined};
119
178
  }
@@ -138,49 +197,86 @@ export default ({hyphenateISBN = false, handleInvalid = false} = {}) => {
138
197
  }, {valid: false, messages: []});
139
198
  }
140
199
 
200
+
141
201
  function fix(record) {
142
- getInvalidFields(record).forEach(field => {
202
+ getRelevantFields(record).forEach(field => {
143
203
  if (field.tag === '020') {
144
- const subfield = field.subfields.find(sf => sf.code === 'a');
145
- if (subfield) {
146
- // ISBN is valid but is missing hyphens
147
- const normalizedValue = normalizeIsbnValue(subfield.value);
148
- if (normalizedValue !== undefined) { // eslint-disable-line functional/no-conditional-statement
149
- subfield.value = normalizedValue; // eslint-disable-line functional/immutable-data
150
- } else if (handleInvalid) { // eslint-disable-line functional/no-conditional-statement
151
- field.subfields.push({code: 'z', value: subfield.value}); // eslint-disable-line functional/immutable-data
152
- record.removeSubfield(subfield, field);
153
- }
204
+ field.subfields.forEach(subfield => fixField020Subfield(field, subfield));
205
+ return;
206
+ }
207
+ // 022 ISSN:
208
+ const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');
209
+ if (subfield && handleInvalid) { // eslint-disable-line functional/no-conditional-statement
210
+ // $a/$l => $y (bit overkill to add $z and remove $a/$l instead of just renaming)
211
+ field.subfields.push({code: 'y', value: subfield.value}); // eslint-disable-line functional/immutable-data
212
+ record.removeSubfield(subfield, field);
213
+ }
214
+ });
215
+
216
+
217
+ function fixField020Subfield(field, subfield) {
218
+ split020A(); // subfield and field are in the scope
219
+ addHyphens(subfield);
220
+ handleInvalidIsbn(field, subfield); // remove 020$a, add 020$z, Do this last, as it uses deletion
221
+ return;
222
+
223
+ function addHyphens(subfield) {
224
+ if (!subfieldsIsbnRequiresHyphenation(subfield)) {
225
+ return;
154
226
  }
155
- } else {
156
- const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');
157
- if (subfield && handleInvalid) { // eslint-disable-line functional/no-conditional-statement
158
- field.subfields.push({code: 'y', value: trimSpaces(subfield.value)}); // eslint-disable-line functional/immutable-data
159
- record.removeSubfield(subfield, field);
227
+ // ISBN is valid but is missing hyphens
228
+ const normalizedValue = normalizeIsbnValue(subfield.value);
229
+ if (normalizedValue !== undefined) { // eslint-disable-line functional/no-conditional-statement
230
+ subfield.value = normalizedValue; // eslint-disable-line functional/immutable-data
160
231
  }
161
232
  }
162
- });
163
233
 
164
- function normalizeIsbnValue(value) {
165
- const trimmedValue = trimISBN(value); // NB! This might lose information that should be stored in $q...
166
- const auditResult = ISBN.audit(trimmedValue);
167
- if (auditResult.validIsbn) {
168
- const parsedIsbn = ISBN.parse(trimmedValue);
169
- if (hyphenateISBN) { // eslint-disable-line functional/no-conditional-statement
170
- return trimmedValue.length === 10 ? parsedIsbn.isbn10h : parsedIsbn.isbn13h; // eslint-disable-line functional/immutable-data
234
+ function handleInvalidIsbn(field, subfield) {
235
+ if (subfield.code !== 'a' || !handleInvalid) {
236
+ return;
237
+ }
238
+ const head = getFirstWord(subfield.value);
239
+ if (!invalidISBN(head)) {
240
+ return;
171
241
  }
172
- // Just trim
173
- return trimmedValue.length === 10 ? parsedIsbn.isbn10 : parsedIsbn.isbn13; // eslint-disable-line functional/immutable-data
242
+ // $a => $z (bit overkill to add $z and remove $a instead of just renaming, but too lazy to fix/test thorougly)
243
+ field.subfields.push({code: 'z', value: subfield.value}); // eslint-disable-line functional/immutable-data
244
+ record.removeSubfield(subfield, field);
174
245
  }
175
- return undefined;
176
- }
177
246
 
178
- function trimSpaces(value) {
179
- return value.replace(/\s/gu, '');
180
- }
247
+ function split020A() {
248
+ // Move non-initial words from $a to $q:
249
+ if (subfield.code !== 'a') {
250
+ return;
251
+ }
252
+ const value = trimSpaces(subfield.value);
253
+ const position = value.indexOf(' ');
254
+ if (position === -1) {
255
+ return;
256
+ }
257
+ const head = getFirstWord(value);
258
+ if (invalidISBN(head)) { // Don't split, if first word ain't ISBN
259
+ return;
260
+ }
261
+ const tail = value.substring(position + 1);
262
+ subfield.value = head; // eslint-disable-line functional/immutable-data
263
+ field.subfields.push({code: 'q', value: tail}); // eslint-disable-line functional/immutable-data
264
+ }
181
265
 
182
- function trimISBN(value) {
183
- return trimSpaces(value.replace(/\s\D+$/gu, '')); // handle "1234567890 (nid.)" => "1234567890" as well as spaces
266
+ function normalizeIsbnValue(value) {
267
+ const trimmedValue = getFirstWord(value);
268
+ //const trimmedValue = trimISBN(value); // NB! This might lose information that should be stored in $q...
269
+ const auditResult = ISBN.audit(trimmedValue);
270
+ if (!auditResult.validIsbn) {
271
+ return undefined;
272
+ }
273
+ const numbersOnly = trimmedValue.replace(/[^0-9Xx]+/ug, '');
274
+ const parsedIsbn = ISBN.parse(trimmedValue);
275
+ if (hyphenateISBN) { // eslint-disable-line functional/no-conditional-statement
276
+ return numbersOnly.length === 10 ? parsedIsbn.isbn10h : parsedIsbn.isbn13h; // eslint-disable-line functional/immutable-data
277
+ }
278
+ return numbersOnly.length === 10 ? parsedIsbn.isbn10 : parsedIsbn.isbn13; // eslint-disable-line functional/immutable-data
279
+ }
184
280
  }
185
281
  }
186
282
  };
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * MARC record validators used in Melinda
6
6
  *
7
- * Copyright (c) 2014-2020 University Of Helsinki (The National Library Of Finland)
7
+ * Copyright (c) 2014-2022 University Of Helsinki (The National Library Of Finland)
8
8
  *
9
9
  * This file is part of marc-record-validators-melinda
10
10
  *
@@ -60,6 +60,7 @@ describe('isbn-issn', () => {
60
60
  ind2: ' ',
61
61
  subfields: [{code: 'a', value: '90-6831-372-X'}]
62
62
  },
63
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: '978-600-377-017-1'}]},
63
64
  {
64
65
  tag: '022',
65
66
  ind1: ' ',
@@ -83,6 +84,12 @@ describe('isbn-issn', () => {
83
84
  ind2: ' ',
84
85
  subfields: [{code: 'a', value: 'foo'}]
85
86
  },
87
+ {
88
+ tag: '020',
89
+ ind1: ' ',
90
+ ind2: ' ',
91
+ subfields: [{code: 'a', value: '90-68-31-372-X'}] // contains an extra hyphen
92
+ },
86
93
  {
87
94
  tag: '022',
88
95
  ind1: ' ',
@@ -101,7 +108,7 @@ describe('isbn-issn', () => {
101
108
  });
102
109
  });
103
110
 
104
- it('Finds the invalid 020 field', async () => {
111
+ it('020 field without $a and $z is ok in this context (= no invalid ISBNs)', async () => {
105
112
  const validator = await validatorFactory();
106
113
  const record = new MarcRecord({
107
114
  fields: [
@@ -116,7 +123,7 @@ describe('isbn-issn', () => {
116
123
  const result = await validator.validate(record);
117
124
 
118
125
  expect(result).to.eql({
119
- valid: false, messages: ['ISBN (undefined) is not valid']
126
+ valid: true
120
127
  });
121
128
  });
122
129
 
@@ -139,16 +146,10 @@ describe('isbn-issn', () => {
139
146
  });
140
147
  });
141
148
 
142
- it('Finds the record invalid (Spaces in ISBN)', async () => {
149
+ it('Finds the record invalid (reason: multiword)', async () => {
143
150
  const validator = await validatorFactory();
144
151
  const record = new MarcRecord({
145
152
  fields: [
146
- {
147
- tag: '020',
148
- ind1: ' ',
149
- ind2: ' ',
150
- subfields: [{code: 'a', value: ' 9789519155470'}]
151
- },
152
153
  {
153
154
  tag: '020',
154
155
  ind1: ' ',
@@ -159,12 +160,7 @@ describe('isbn-issn', () => {
159
160
  });
160
161
  const result = await validator.validate(record);
161
162
 
162
- expect(result).to.eql({
163
- valid: false, messages: [
164
- 'ISBN ( 9789519155470) is not valid',
165
- 'ISBN (978-600-377-017-1 (nid.)) is not valid'
166
- ]
167
- });
163
+ expect(result).to.eql({valid: false, messages: ['ISBN (978-600-377-017-1 (nid.)) is not valid']});
168
164
  });
169
165
 
170
166
  it('Finds the record invalid (ISSN in \'l\'-subfield)', async () => {
@@ -205,6 +201,12 @@ describe('isbn-issn', () => {
205
201
  ind2: ' ',
206
202
  subfields: [{code: 'a', value: '9789519155470'}]
207
203
  },
204
+ {
205
+ tag: '020',
206
+ ind1: ' ',
207
+ ind2: ' ',
208
+ subfields: [{code: 'a', value: '9068-31-372-X'}] // legal digits, but bad hyphenation
209
+ },
208
210
  {
209
211
  tag: '020',
210
212
  ind1: ' ',
@@ -217,6 +219,7 @@ describe('isbn-issn', () => {
217
219
 
218
220
  expect(result).to.eql({valid: false, messages: [
219
221
  'ISBN (9789519155470) is not valid',
222
+ 'ISBN (9068-31-372-X) is not valid',
220
223
  'ISBN (386006004X) is not valid'
221
224
  ]});
222
225
  });
@@ -237,7 +240,10 @@ describe('isbn-issn', () => {
237
240
  {
238
241
  tag: '020', ind1: ' ', ind2: ' ',
239
242
  subfields: [{code: 'a', value: 'crappy val'}]
240
- }
243
+ },
244
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '97895234216609'}]},
245
+ // Just a sanity check due to earlier issues:
246
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: '97895234216609'}]}
241
247
  ]
242
248
  });
243
249
 
@@ -245,7 +251,9 @@ describe('isbn-issn', () => {
245
251
 
246
252
  expect(record.fields).to.eql([
247
253
  {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: 'foo'}]},
248
- {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: 'crappy val'}]}
254
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: 'crappy val'}]},
255
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: '97895234216609'}]},
256
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: '97895234216609'}]}
249
257
  ]);
250
258
  });
251
259
 
@@ -303,15 +311,16 @@ describe('isbn-issn', () => {
303
311
  tag: '020',
304
312
  ind1: ' ',
305
313
  ind2: ' ',
306
- subfields: [{code: 'a', value: '9786003770171 (nid.)'}]
314
+ subfields: [{code: 'a', value: '9786003770171 (nidottu)'}]
307
315
  }
308
316
  ]
309
317
  });
310
318
  await validator.fix(record);
311
319
 
312
320
  expect(record.fields).to.eql([
313
- {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '9786003770171'}]},
314
- {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '9786003770171'}]}
321
+ // NB! Initial space does not need to be removed. It's crap, but not this fixer's crap.
322
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: ' 9786003770171'}]},
323
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '9786003770171'}, {code: 'q', value: '(nidottu)'}]}
315
324
  ]);
316
325
  });
317
326
 
@@ -323,7 +332,11 @@ describe('isbn-issn', () => {
323
332
  tag: '020',
324
333
  ind1: ' ',
325
334
  ind2: ' ',
326
- subfields: [{code: 'a', value: ' 9786003770171'}]
335
+ subfields: [
336
+ {code: 'a', value: '9786003770171 (nid.)'},
337
+ {code: 'z', value: '9786003770171 (nid.)'},
338
+ {code: 'z', value: 'foo bar'}
339
+ ]
327
340
  }
328
341
  ]
329
342
  });
@@ -331,12 +344,52 @@ describe('isbn-issn', () => {
331
344
 
332
345
  expect(record.fields).to.eql([
333
346
  {
334
- tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '978-600-377-017-1'}]
347
+ tag: '020',
348
+ ind1: ' ',
349
+ ind2: ' ',
350
+ subfields: [
351
+ {code: 'a', value: '978-600-377-017-1'},
352
+ {code: 'z', value: '978-600-377-017-1'},
353
+ {code: 'z', value: 'foo bar'},
354
+ // NB! Technically $q should come before $z subfields, but this is good enough.
355
+ {code: 'q', value: '(nid.)'}
356
+ ]
335
357
  }
336
358
  ]);
337
359
  });
338
360
 
339
- it('Adds hyphens to ISBN', async () => {
361
+ it('No relevant data', async () => {
362
+ const validator = await validatorFactory({hyphenateISBN: true});
363
+ const record = new MarcRecord({
364
+ fields: [
365
+ {
366
+ tag: '005',
367
+ value: 'whatever'
368
+ },
369
+ {
370
+ tag: '020',
371
+ ind1: ' ',
372
+ ind2: ' ',
373
+ subfields: [{code: 'q', value: 'sidottu'}]
374
+ },
375
+ {
376
+ tag: '024',
377
+ ind1: ' ',
378
+ ind2: ' ',
379
+ subfields: [{code: 'a', value: ' 9786003770171'}]
380
+ }
381
+ ]
382
+ });
383
+ await validator.fix(record);
384
+
385
+ expect(record.fields).to.eql([
386
+ {tag: '005', value: 'whatever'},
387
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'q', value: 'sidottu'}]},
388
+ {tag: '024', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: ' 9786003770171'}]}
389
+ ]);
390
+ });
391
+
392
+ it('Add hyphens to ISBN', async () => {
340
393
  const validator = await validatorFactory({hyphenateISBN: true});
341
394
  const record = new MarcRecord({
342
395
  fields: [
@@ -348,10 +401,21 @@ describe('isbn-issn', () => {
348
401
  tag: '020', ind1: ' ', ind2: ' ',
349
402
  subfields: [{code: 'a', value: '917153086X'}]
350
403
  },
404
+ {
405
+ tag: '020',
406
+ ind1: ' ',
407
+ ind2: ' ',
408
+ subfields: [{code: 'a', value: '9068-31-372-X'}] // legal digits, but bad hyphenation
409
+ },
351
410
  {
352
411
  tag: '020', ind1: ' ', ind2: ' ',
353
412
  subfields: [{code: 'a', value: '386006004X (nid.)'}]
354
- }
413
+ },
414
+ {
415
+ tag: '020', ind1: ' ', ind2: ' ',
416
+ subfields: [{code: 'z', value: '9789916605325'}]
417
+ },
418
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: '9789916605325 (sid.)'}]}
355
419
  ]
356
420
  });
357
421
 
@@ -360,7 +424,10 @@ describe('isbn-issn', () => {
360
424
  expect(record.fields).to.eql([
361
425
  {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '978-9916-605-32-5'}]},
362
426
  {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '91-7153-086-X'}]},
363
- {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '3-86006-004-X'}]}
427
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '90-6831-372-X'}]}, // corrected hyphens
428
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '3-86006-004-X'}, {code: 'q', value: '(nid.)'}]},
429
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: '978-9916-605-32-5'}]},
430
+ {tag: '020', ind1: ' ', ind2: ' ', subfields: [{code: 'z', value: '978-9916-605-32-5'}]}
364
431
  ]);
365
432
  });
366
433
  });