@natlibfi/marc-record-validators-melinda 9.0.5-alpha.1 → 9.0.7-alpha.1

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/dist/isbn-issn.js CHANGED
@@ -49,88 +49,87 @@ var _default = ({
49
49
  description: 'Validates ISBN and ISSN values'
50
50
  };
51
51
 
52
+ function stringHasSpace(str) {
53
+ return str.indexOf(' ') > -1;
54
+ }
55
+
56
+ function trimSpaces(value) {
57
+ return value.replace(/^\s+/u, '').replace(/\s+$/u, '').replace(/\s+/gu, ' ');
58
+ }
59
+
60
+ function isMultiWord(inputString) {
61
+ const trimmedString = trimSpaces(inputString);
62
+ return stringHasSpace(trimmedString);
63
+ }
64
+
65
+ function getFirstWord(inputString) {
66
+ const trimmedString = trimSpaces(inputString);
67
+ const arr = trimmedString.split(' ');
68
+ return arr[0];
69
+ }
70
+
52
71
  function invalidISBN(isbn) {
53
- // If value contains space, it's not necessarily crap. (It's typically something like "1234567890 (nid.)".)
54
- // Check the first word in string:
55
- if (isbn.indexOf(' ') > -1) {
56
- // const arr = isbn.split(' ');
57
- // console.info(`invalidISBN(): Check '${arr[0]}' instead of '${isbn}'`); // eslint-disable-line no-console
58
- // return invalidISBN(arr[0]);
59
- return true;
60
- }
72
+ const isbnOnly = getFirstWord(isbn);
61
73
 
62
- const auditedIsbn = _isbn.default.audit(isbn);
74
+ const auditedIsbn = _isbn.default.audit(isbnOnly);
63
75
 
64
- if (!auditedIsbn.validIsbn) {
65
- console.info(`Invalid ISBN detected: ${isbn}`); // eslint-disable-line no-console
76
+ return !auditedIsbn.validIsbn;
77
+ }
66
78
 
67
- return true;
79
+ function invalidSubfield(subfield) {
80
+ if (subfield.code !== 'a') {
81
+ return false;
68
82
  }
69
83
 
70
- return false;
84
+ return invalidISBN(subfield.value) || isMultiWord(subfield.value);
71
85
  }
72
86
 
73
87
  function invalidField020(field) {
74
- if (field.subfields && field.subfields.some(sf => invalidField020a(sf) || invalidField020z(sf))) {
88
+ if (field.subfields && field.subfields.some(sf => invalidSubfield(sf))) {
75
89
  return true;
76
90
  }
77
91
 
78
92
  return false;
93
+ }
79
94
 
80
- function invalidField020a(subfield) {
81
- if (subfield.code !== 'a') {
82
- return false;
83
- }
84
-
85
- if (invalidISBN(subfield.value)) {
86
- return true;
87
- }
88
-
95
+ function subfieldsIsbnRequiresHyphenation(subfield) {
96
+ if (!hyphenateISBN || !['a', 'z'].includes(subfield.code)) {
89
97
  return false;
90
98
  }
91
99
 
92
- function invalidField020z(subfield) {
93
- if (subfield.code !== 'z' || !hyphenateISBN || invalidISBN(subfield.value)) {
94
- return false;
95
- } // We are only interested in $z field if it is valid ISBN that requires hyphenation:
100
+ const isbn = getFirstWord(subfield.value);
96
101
 
102
+ if (subfield.code === 'a') {
103
+ return requiresHyphenation(isbn);
104
+ } // $z is a bit hacky: hyphenation is required only iff valid and no '-' chars
97
105
 
98
- return subfield.value.indexOf('-') === -1;
99
- }
100
- }
101
106
 
102
- function subfieldRequiresHyphenation(subfield) {
103
- if (subfield.code !== 'a' && subfield.code !== 'z') {
107
+ if (isbn.indexOf('-') > -1) {
104
108
  return false;
105
109
  }
106
110
 
107
- return requiresHyphenation(subfield.value);
111
+ return !invalidISBN(isbn);
108
112
 
109
113
  function requiresHyphenation(isbn) {
110
- if (isbn.indexOf(' ') > -1) {
111
- const arr = isbn.split(' ');
112
- console.info(`requiresHyphenation(): Check '${arr[0]}' instead of '${isbn}'`); // eslint-disable-line no-console
114
+ if (!hyphenateISBN) {
115
+ return false;
116
+ } // Handle old notation such as "978-952-396-001-5 (nid.)"
113
117
 
114
- return requiresHyphenation(arr[0]);
115
- }
116
118
 
117
- if (invalidISBN(isbn)) {
119
+ const isbn2 = getFirstWord(isbn);
120
+
121
+ if (invalidISBN(isbn2)) {
118
122
  return false;
119
123
  }
120
124
 
121
- console.info(`sfRH ${isbn}`); // eslint-disable-line no-console
125
+ const parsedIsbn = _isbn.default.parse(isbn2); // Return true only if existing ISBN is a valid and hyphenated 10 or 13 digit ISBN:
122
126
 
123
- const parsedIsbn = _isbn.default.parse(isbn);
124
-
125
- if (hyphenateISBN) {
126
- return !(isbn === parsedIsbn.isbn10h || isbn === parsedIsbn.isbn13h);
127
- }
128
127
 
129
- return false; //return !(isbn === parsedIsbn.isbn10 || isbn === parsedIsbn.isbn13);
128
+ return !(isbn2 === parsedIsbn.isbn10h || isbn2 === parsedIsbn.isbn13h);
130
129
  }
131
130
  }
132
131
 
133
- function getInvalidFields(record) {
132
+ function getRelevantFields(record) {
134
133
  //return record.get(/^(?:020|022)$/u).filter(field => {
135
134
  return record.fields.filter(field => {
136
135
  if (!field.subfields) {
@@ -140,10 +139,11 @@ var _default = ({
140
139
 
141
140
  if (field.tag === '020') {
142
141
  if (invalidField020(field)) {
142
+ // checks multiwordness
143
143
  return true;
144
144
  }
145
145
 
146
- return fieldRequiresHyphenation(field);
146
+ return fieldsIsbnRequiresHyphenation(field);
147
147
  } // Check ISSN:
148
148
 
149
149
 
@@ -159,8 +159,8 @@ var _default = ({
159
159
  return false;
160
160
  });
161
161
 
162
- function fieldRequiresHyphenation(field) {
163
- return field.subfields && field.subfields.some(sf => subfieldRequiresHyphenation(sf));
162
+ function fieldsIsbnRequiresHyphenation(field) {
163
+ return field.subfields && field.subfields.some(sf => subfieldsIsbnRequiresHyphenation(sf));
164
164
  }
165
165
 
166
166
  function invalidField022(field) {
@@ -181,7 +181,7 @@ var _default = ({
181
181
  }
182
182
 
183
183
  function validate(record) {
184
- const fields = getInvalidFields(record);
184
+ const fields = getRelevantFields(record);
185
185
 
186
186
  if (fields.length === 0) {
187
187
  return {
@@ -200,6 +200,15 @@ var _default = ({
200
200
  };
201
201
  }
202
202
 
203
+ const subfieldZ = field.subfields.find(sf => sf.code === 'z');
204
+
205
+ if (subfieldZ) {
206
+ return {
207
+ name: 'ISBN (subfield Z)',
208
+ value: subfieldZ.value
209
+ };
210
+ }
211
+
203
212
  return {
204
213
  name: 'ISBN',
205
214
  value: undefined
@@ -236,7 +245,7 @@ var _default = ({
236
245
  }
237
246
 
238
247
  function fix(record) {
239
- getInvalidFields(record).forEach(field => {
248
+ getRelevantFields(record).forEach(field => {
240
249
  if (field.tag === '020') {
241
250
  field.subfields.forEach(subfield => fixField020Subfield(field, subfield));
242
251
  return;
@@ -256,67 +265,97 @@ var _default = ({
256
265
  record.removeSubfield(subfield, field);
257
266
  }
258
267
  });
259
- /*
260
- function trimISBN(value) {
261
- return trimSpaces(value.replace(/\s$/gu, '')); // handle "1234567890 (nid.)" => "1234567890" as well as spaces
262
- }
263
- */
264
268
 
265
269
  function fixField020Subfield(field, subfield) {
266
- console.info(`fixField020Subfield ${subfield.code} '${subfield.value}'`); // eslint-disable-line no-console
270
+ split020A(); // subfield and field are in the scope
271
+
272
+ addHyphens(subfield);
273
+ handleInvalidIsbn(field, subfield); // remove 020$a, add 020$z, Do this last, as it uses deletion
274
+
275
+ return;
276
+
277
+ function addHyphens(subfield) {
278
+ if (!subfieldsIsbnRequiresHyphenation(subfield)) {
279
+ return;
280
+ } // ISBN is valid but is missing hyphens
267
281
 
268
- if (invalidISBN(subfield.value) || subfieldRequiresHyphenation(subfield)) {
269
- console.info(` fixField020Subfield ${subfield.code} '${subfield.value}'`); // eslint-disable-line no-console
270
- // ISBN is valid but is missing hyphens
271
282
 
272
283
  const normalizedValue = normalizeIsbnValue(subfield.value);
273
284
 
274
285
  if (normalizedValue !== undefined) {
275
286
  // eslint-disable-line functional/no-conditional-statement
276
287
  subfield.value = normalizedValue; // eslint-disable-line functional/immutable-data
277
- } else if (subfield.code === 'a' && handleInvalid) {
278
- // eslint-disable-line functional/no-conditional-statement
279
- // $a => $z (bit overkill to add $z and remove $a instead of just renaming, but too lazy to fix/test thorougly)
280
- field.subfields.push({
281
- code: 'z',
282
- value: subfield.value
283
- }); // eslint-disable-line functional/immutable-data
284
-
285
- record.removeSubfield(subfield, field);
286
288
  }
287
289
  }
288
290
 
289
- return;
291
+ function handleInvalidIsbn(field, subfield) {
292
+ if (subfield.code !== 'a' || !handleInvalid) {
293
+ return;
294
+ }
290
295
 
291
- function normalizeIsbnValue2(trimmedValue) {
292
- //const trimmedValue = trimISBN(value); // NB! This might lose information that should be stored in $q...
293
- const auditResult = _isbn.default.audit(trimmedValue);
296
+ const head = getFirstWord(subfield.value);
294
297
 
295
- if (auditResult.validIsbn) {
296
- const parsedIsbn = _isbn.default.parse(trimmedValue);
298
+ if (!invalidISBN(head)) {
299
+ return;
300
+ } // $a => $z (bit overkill to add $z and remove $a instead of just renaming, but too lazy to fix/test thorougly)
297
301
 
298
- if (hyphenateISBN) {
299
- // eslint-disable-line functional/no-conditional-statement
300
- return trimmedValue.length === 10 ? parsedIsbn.isbn10h : parsedIsbn.isbn13h; // eslint-disable-line functional/immutable-data
301
- } // Just trim
302
302
 
303
+ field.subfields.push({
304
+ code: 'z',
305
+ value: subfield.value
306
+ }); // eslint-disable-line functional/immutable-data
307
+
308
+ record.removeSubfield(subfield, field);
309
+ }
303
310
 
304
- return trimmedValue.length === 10 ? parsedIsbn.isbn10 : parsedIsbn.isbn13; // eslint-disable-line functional/immutable-data
311
+ function split020A() {
312
+ // Move non-initial words from $a to $q:
313
+ if (subfield.code !== 'a') {
314
+ return;
305
315
  }
306
316
 
307
- return undefined;
317
+ const value = trimSpaces(subfield.value);
318
+ const position = value.indexOf(' ');
319
+
320
+ if (position === -1) {
321
+ return;
322
+ }
323
+
324
+ const head = getFirstWord(value);
325
+
326
+ if (invalidISBN(head)) {
327
+ // Don't split, if first word ain't ISBN
328
+ return;
329
+ }
330
+
331
+ const tail = value.substring(position + 1);
332
+ subfield.value = head; // eslint-disable-line functional/immutable-data
333
+
334
+ field.subfields.push({
335
+ code: 'q',
336
+ value: tail
337
+ }); // eslint-disable-line functional/immutable-data
308
338
  }
309
339
 
310
340
  function normalizeIsbnValue(value) {
311
- const trimmedValue = value.replace(/^\s+/gu, '');
341
+ const trimmedValue = getFirstWord(value); //const trimmedValue = trimISBN(value); // NB! This might lose information that should be stored in $q...
342
+
343
+ const auditResult = _isbn.default.audit(trimmedValue);
312
344
 
313
- if (trimmedValue.indexOf(' ') === -1) {
314
- return normalizeIsbnValue2(trimmedValue);
345
+ if (!auditResult.validIsbn) {
346
+ return undefined;
315
347
  }
316
348
 
317
- const [head] = trimmedValue.split(' '); // NB! We currently drop the tail part, as it prevents us from pairing doubles. Parametrize?
349
+ const numbersOnly = trimmedValue.replace(/[^0-9Xx]+/ug, '');
350
+
351
+ const parsedIsbn = _isbn.default.parse(trimmedValue);
352
+
353
+ if (hyphenateISBN) {
354
+ // eslint-disable-line functional/no-conditional-statement
355
+ return numbersOnly.length === 10 ? parsedIsbn.isbn10h : parsedIsbn.isbn13h; // eslint-disable-line functional/immutable-data
356
+ }
318
357
 
319
- return normalizeIsbnValue2(head);
358
+ return numbersOnly.length === 10 ? parsedIsbn.isbn10 : parsedIsbn.isbn13; // eslint-disable-line functional/immutable-data
320
359
  }
321
360
  }
322
361
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/isbn-issn.js"],"names":["hyphenateISBN","handleInvalid","validate","fix","description","invalidISBN","isbn","indexOf","auditedIsbn","ISBN","audit","validIsbn","console","info","invalidField020","field","subfields","some","sf","invalidField020a","invalidField020z","subfield","code","value","subfieldRequiresHyphenation","requiresHyphenation","arr","split","parsedIsbn","parse","isbn10h","isbn13h","getInvalidFields","record","fields","filter","tag","fieldRequiresHyphenation","invalidField022","find","subfieldAorL","undefined","subfieldY","length","valid","map","subfieldA","name","getISSN","reduce","acc","obj","msg","messages","concat","forEach","fixField020Subfield","push","removeSubfield","normalizedValue","normalizeIsbnValue","normalizeIsbnValue2","trimmedValue","auditResult","isbn10","isbn13","replace","head"],"mappings":"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","sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* MARC record validators used in Melinda\n*\n* Copyright (c) 2014-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of marc-record-validators-melinda\n*\n* marc-record-validators-melinda program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* marc-record-validators-melinda is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Lesser General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport ISBN from 'isbn3';\nimport validateISSN from '@natlibfi/issn-verify';\n\n// handleInvalid: move invalid 020$a to 020$z, and invalid 022$a to 022$y\nexport default ({hyphenateISBN = false, handleInvalid = false} = {}) => {\n return {\n validate, fix,\n description: 'Validates ISBN and ISSN values'\n };\n\n function invalidISBN(isbn) {\n // If value contains space, it's not necessarily crap. (It's typically something like \"1234567890 (nid.)\".)\n // Check the first word in string:\n if (isbn.indexOf(' ') > -1) {\n // const arr = isbn.split(' ');\n // console.info(`invalidISBN(): Check '${arr[0]}' instead of '${isbn}'`); // eslint-disable-line no-console\n // return invalidISBN(arr[0]);\n return true;\n }\n\n const auditedIsbn = ISBN.audit(isbn);\n if (!auditedIsbn.validIsbn) {\n console.info(`Invalid ISBN detected: ${isbn}`); // eslint-disable-line no-console\n return true;\n }\n return false;\n }\n\n\n function invalidField020(field) {\n if (field.subfields && field.subfields.some(sf => invalidField020a(sf) || invalidField020z(sf))) {\n return true;\n }\n return false;\n\n function invalidField020a(subfield) {\n if (subfield.code !== 'a') {\n return false;\n }\n if (invalidISBN(subfield.value)) {\n return true;\n }\n return false;\n }\n\n function invalidField020z(subfield) {\n if (subfield.code !== 'z' || !hyphenateISBN || invalidISBN(subfield.value)) {\n return false;\n }\n // We are only interested in $z field if it is valid ISBN that requires hyphenation:\n return subfield.value.indexOf('-') === -1;\n }\n }\n\n function subfieldRequiresHyphenation(subfield) {\n if (subfield.code !== 'a' && subfield.code !== 'z') {\n return false;\n }\n return requiresHyphenation(subfield.value);\n\n function requiresHyphenation(isbn) {\n if (isbn.indexOf(' ') > -1) {\n const arr = isbn.split(' ');\n console.info(`requiresHyphenation(): Check '${arr[0]}' instead of '${isbn}'`); // eslint-disable-line no-console\n return requiresHyphenation(arr[0]);\n }\n\n if (invalidISBN(isbn)) {\n return false;\n }\n console.info(`sfRH ${isbn}`); // eslint-disable-line no-console\n const parsedIsbn = ISBN.parse(isbn);\n if (hyphenateISBN) {\n return !(isbn === parsedIsbn.isbn10h || isbn === parsedIsbn.isbn13h);\n }\n return false;\n //return !(isbn === parsedIsbn.isbn10 || isbn === parsedIsbn.isbn13);\n }\n }\n\n function getInvalidFields(record) {\n //return record.get(/^(?:020|022)$/u).filter(field => {\n return record.fields.filter(field => {\n if (!field.subfields) {\n return false;\n }\n // Check ISBN:\n if (field.tag === '020') {\n if (invalidField020(field)) {\n return true;\n }\n return fieldRequiresHyphenation(field);\n }\n\n // Check ISSN:\n if (field.tag === '022') {\n if (invalidField022(field)) {\n return true;\n }\n\n const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n\n return !validateISSN(subfield.value);\n }\n return false;\n });\n\n function fieldRequiresHyphenation(field) {\n return field.subfields && field.subfields.some(sf => subfieldRequiresHyphenation(sf));\n }\n\n function invalidField022(field) {\n const subfieldAorL = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n\n if (subfieldAorL === undefined) {\n const subfieldY = field.subfields.find(sf => sf.code === 'y');\n if (subfieldY) {\n return false;\n }\n\n return true;\n }\n return false;\n }\n }\n\n function validate(record) {\n const fields = getInvalidFields(record);\n\n if (fields.length === 0) {\n return {valid: true};\n }\n\n return fields\n .map(field => {\n if (field.tag === '020') {\n const subfieldA = field.subfields.find(sf => sf.code === 'a');\n if (subfieldA) {\n return {name: 'ISBN', value: subfieldA.value};\n }\n\n return {name: 'ISBN', value: undefined};\n }\n\n return {name: 'ISSN', value: getISSN()};\n\n function getISSN() {\n const subfieldAorL = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n\n if (subfieldAorL) {\n return subfieldAorL.value;\n }\n\n return undefined;\n }\n })\n .reduce((acc, obj) => {\n const {name, value} = obj;\n const msg = `${name} (${value}) is not valid`;\n\n return {...acc, messages: acc.messages.concat(msg)};\n }, {valid: false, messages: []});\n }\n\n\n function fix(record) {\n getInvalidFields(record).forEach(field => {\n if (field.tag === '020') {\n field.subfields.forEach(subfield => fixField020Subfield(field, subfield));\n return;\n }\n // 022 ISSN:\n const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n if (subfield && handleInvalid) { // eslint-disable-line functional/no-conditional-statement\n // $a/$l => $y (bit overkill to add $z and remove $a/$l instead of just renaming)\n field.subfields.push({code: 'y', value: subfield.value}); // eslint-disable-line functional/immutable-data\n record.removeSubfield(subfield, field);\n }\n });\n\n /*\n function trimISBN(value) {\n return trimSpaces(value.replace(/\\s$/gu, '')); // handle \"1234567890 (nid.)\" => \"1234567890\" as well as spaces\n }\n */\n\n function fixField020Subfield(field, subfield) {\n console.info(`fixField020Subfield ${subfield.code} '${subfield.value}'`); // eslint-disable-line no-console\n if (invalidISBN(subfield.value) || subfieldRequiresHyphenation(subfield)) {\n console.info(` fixField020Subfield ${subfield.code} '${subfield.value}'`); // eslint-disable-line no-console\n // ISBN is valid but is missing hyphens\n const normalizedValue = normalizeIsbnValue(subfield.value);\n if (normalizedValue !== undefined) { // eslint-disable-line functional/no-conditional-statement\n subfield.value = normalizedValue; // eslint-disable-line functional/immutable-data\n } else if (subfield.code === 'a' && handleInvalid) { // eslint-disable-line functional/no-conditional-statement\n // $a => $z (bit overkill to add $z and remove $a instead of just renaming, but too lazy to fix/test thorougly)\n field.subfields.push({code: 'z', value: subfield.value}); // eslint-disable-line functional/immutable-data\n record.removeSubfield(subfield, field);\n }\n }\n return;\n\n function normalizeIsbnValue2(trimmedValue) {\n //const trimmedValue = trimISBN(value); // NB! This might lose information that should be stored in $q...\n const auditResult = ISBN.audit(trimmedValue);\n if (auditResult.validIsbn) {\n const parsedIsbn = ISBN.parse(trimmedValue);\n if (hyphenateISBN) { // eslint-disable-line functional/no-conditional-statement\n return trimmedValue.length === 10 ? parsedIsbn.isbn10h : parsedIsbn.isbn13h; // eslint-disable-line functional/immutable-data\n }\n // Just trim\n return trimmedValue.length === 10 ? parsedIsbn.isbn10 : parsedIsbn.isbn13; // eslint-disable-line functional/immutable-data\n }\n return undefined;\n }\n\n function normalizeIsbnValue(value) {\n const trimmedValue = value.replace(/^\\s+/gu, '');\n if (trimmedValue.indexOf(' ') === -1) {\n return normalizeIsbnValue2(trimmedValue);\n }\n const [head] = trimmedValue.split(' ');\n // NB! We currently drop the tail part, as it prevents us from pairing doubles. Parametrize?\n return normalizeIsbnValue2(head);\n }\n }\n }\n};\n"],"file":"isbn-issn.js"}
1
+ {"version":3,"sources":["../src/isbn-issn.js"],"names":["hyphenateISBN","handleInvalid","validate","fix","description","stringHasSpace","str","indexOf","trimSpaces","value","replace","isMultiWord","inputString","trimmedString","getFirstWord","arr","split","invalidISBN","isbn","isbnOnly","auditedIsbn","ISBN","audit","validIsbn","invalidSubfield","subfield","code","invalidField020","field","subfields","some","sf","subfieldsIsbnRequiresHyphenation","includes","requiresHyphenation","isbn2","parsedIsbn","parse","isbn10h","isbn13h","getRelevantFields","record","fields","filter","tag","fieldsIsbnRequiresHyphenation","invalidField022","find","subfieldAorL","undefined","subfieldY","length","valid","map","subfieldA","name","subfieldZ","getISSN","reduce","acc","obj","msg","messages","concat","forEach","fixField020Subfield","push","removeSubfield","split020A","addHyphens","handleInvalidIsbn","normalizedValue","normalizeIsbnValue","head","position","tail","substring","trimmedValue","auditResult","numbersOnly","isbn10","isbn13"],"mappings":"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","sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* MARC record validators used in Melinda\n*\n* Copyright (c) 2014-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of marc-record-validators-melinda\n*\n* marc-record-validators-melinda program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* marc-record-validators-melinda is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Lesser General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport ISBN from 'isbn3';\nimport validateISSN from '@natlibfi/issn-verify';\n\n// handleInvalid: move invalid 020$a to 020$z, and invalid 022$a to 022$y\nexport default ({hyphenateISBN = false, handleInvalid = false} = {}) => {\n return {\n validate, fix,\n description: 'Validates ISBN and ISSN values'\n };\n\n function stringHasSpace(str) {\n return str.indexOf(' ') > -1;\n }\n\n function trimSpaces(value) {\n return value.replace(/^\\s+/u, '').replace(/\\s+$/u, '').replace(/\\s+/gu, ' ');\n }\n\n function isMultiWord(inputString) {\n const trimmedString = trimSpaces(inputString);\n return stringHasSpace(trimmedString);\n }\n\n function getFirstWord(inputString) {\n const trimmedString = trimSpaces(inputString);\n const arr = trimmedString.split(' ');\n return arr[0];\n }\n\n function invalidISBN(isbn) {\n const isbnOnly = getFirstWord(isbn);\n const auditedIsbn = ISBN.audit(isbnOnly);\n return !auditedIsbn.validIsbn;\n }\n\n function invalidSubfield(subfield) {\n if (subfield.code !== 'a') {\n return false;\n }\n return invalidISBN(subfield.value) || isMultiWord(subfield.value);\n }\n\n\n function invalidField020(field) {\n if (field.subfields && field.subfields.some(sf => invalidSubfield(sf))) {\n return true;\n }\n return false;\n }\n\n function subfieldsIsbnRequiresHyphenation(subfield) {\n if (!hyphenateISBN || !['a', 'z'].includes(subfield.code)) {\n return false;\n }\n\n const isbn = getFirstWord(subfield.value);\n if (subfield.code === 'a') {\n return requiresHyphenation(isbn);\n }\n\n // $z is a bit hacky: hyphenation is required only iff valid and no '-' chars\n if (isbn.indexOf('-') > -1) {\n return false;\n }\n return !invalidISBN(isbn);\n\n function requiresHyphenation(isbn) {\n if (!hyphenateISBN) {\n return false;\n }\n // Handle old notation such as \"978-952-396-001-5 (nid.)\"\n const isbn2 = getFirstWord(isbn);\n\n if (invalidISBN(isbn2)) {\n return false;\n }\n\n const parsedIsbn = ISBN.parse(isbn2);\n // Return true only if existing ISBN is a valid and hyphenated 10 or 13 digit ISBN:\n return !(isbn2 === parsedIsbn.isbn10h || isbn2 === parsedIsbn.isbn13h);\n }\n }\n\n function getRelevantFields(record) {\n //return record.get(/^(?:020|022)$/u).filter(field => {\n return record.fields.filter(field => {\n if (!field.subfields) {\n return false;\n }\n // Check ISBN:\n if (field.tag === '020') {\n if (invalidField020(field)) { // checks multiwordness\n return true;\n }\n return fieldsIsbnRequiresHyphenation(field);\n }\n\n // Check ISSN:\n if (field.tag === '022') {\n if (invalidField022(field)) {\n return true;\n }\n\n const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n\n return !validateISSN(subfield.value);\n }\n return false;\n });\n\n function fieldsIsbnRequiresHyphenation(field) {\n return field.subfields && field.subfields.some(sf => subfieldsIsbnRequiresHyphenation(sf));\n }\n\n function invalidField022(field) {\n const subfieldAorL = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n\n if (subfieldAorL === undefined) {\n const subfieldY = field.subfields.find(sf => sf.code === 'y');\n if (subfieldY) {\n return false;\n }\n\n return true;\n }\n return false;\n }\n }\n\n function validate(record) {\n const fields = getRelevantFields(record);\n\n if (fields.length === 0) {\n return {valid: true};\n }\n\n return fields\n .map(field => {\n if (field.tag === '020') {\n const subfieldA = field.subfields.find(sf => sf.code === 'a');\n if (subfieldA) {\n return {name: 'ISBN', value: subfieldA.value};\n }\n const subfieldZ = field.subfields.find(sf => sf.code === 'z');\n if (subfieldZ) {\n return {name: 'ISBN (subfield Z)', value: subfieldZ.value};\n }\n\n return {name: 'ISBN', value: undefined};\n }\n\n return {name: 'ISSN', value: getISSN()};\n\n function getISSN() {\n const subfieldAorL = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n\n if (subfieldAorL) {\n return subfieldAorL.value;\n }\n\n return undefined;\n }\n })\n .reduce((acc, obj) => {\n const {name, value} = obj;\n const msg = `${name} (${value}) is not valid`;\n\n return {...acc, messages: acc.messages.concat(msg)};\n }, {valid: false, messages: []});\n }\n\n\n function fix(record) {\n getRelevantFields(record).forEach(field => {\n if (field.tag === '020') {\n field.subfields.forEach(subfield => fixField020Subfield(field, subfield));\n return;\n }\n // 022 ISSN:\n const subfield = field.subfields.find(sf => sf.code === 'a' || sf.code === 'l');\n if (subfield && handleInvalid) { // eslint-disable-line functional/no-conditional-statement\n // $a/$l => $y (bit overkill to add $z and remove $a/$l instead of just renaming)\n field.subfields.push({code: 'y', value: subfield.value}); // eslint-disable-line functional/immutable-data\n record.removeSubfield(subfield, field);\n }\n });\n\n\n function fixField020Subfield(field, subfield) {\n split020A(); // subfield and field are in the scope\n addHyphens(subfield);\n handleInvalidIsbn(field, subfield); // remove 020$a, add 020$z, Do this last, as it uses deletion\n return;\n\n function addHyphens(subfield) {\n if (!subfieldsIsbnRequiresHyphenation(subfield)) {\n return;\n }\n // ISBN is valid but is missing hyphens\n const normalizedValue = normalizeIsbnValue(subfield.value);\n if (normalizedValue !== undefined) { // eslint-disable-line functional/no-conditional-statement\n subfield.value = normalizedValue; // eslint-disable-line functional/immutable-data\n }\n }\n\n function handleInvalidIsbn(field, subfield) {\n if (subfield.code !== 'a' || !handleInvalid) {\n return;\n }\n const head = getFirstWord(subfield.value);\n if (!invalidISBN(head)) {\n return;\n }\n // $a => $z (bit overkill to add $z and remove $a instead of just renaming, but too lazy to fix/test thorougly)\n field.subfields.push({code: 'z', value: subfield.value}); // eslint-disable-line functional/immutable-data\n record.removeSubfield(subfield, field);\n }\n\n function split020A() {\n // Move non-initial words from $a to $q:\n if (subfield.code !== 'a') {\n return;\n }\n const value = trimSpaces(subfield.value);\n const position = value.indexOf(' ');\n if (position === -1) {\n return;\n }\n const head = getFirstWord(value);\n if (invalidISBN(head)) { // Don't split, if first word ain't ISBN\n return;\n }\n const tail = value.substring(position + 1);\n subfield.value = head; // eslint-disable-line functional/immutable-data\n field.subfields.push({code: 'q', value: tail}); // eslint-disable-line functional/immutable-data\n }\n\n function normalizeIsbnValue(value) {\n const trimmedValue = getFirstWord(value);\n //const trimmedValue = trimISBN(value); // NB! This might lose information that should be stored in $q...\n const auditResult = ISBN.audit(trimmedValue);\n if (!auditResult.validIsbn) {\n return undefined;\n }\n const numbersOnly = trimmedValue.replace(/[^0-9Xx]+/ug, '');\n const parsedIsbn = ISBN.parse(trimmedValue);\n if (hyphenateISBN) { // eslint-disable-line functional/no-conditional-statement\n return numbersOnly.length === 10 ? parsedIsbn.isbn10h : parsedIsbn.isbn13h; // eslint-disable-line functional/immutable-data\n }\n return numbersOnly.length === 10 ? parsedIsbn.isbn10 : parsedIsbn.isbn13; // eslint-disable-line functional/immutable-data\n }\n }\n }\n};\n"],"file":"isbn-issn.js"}
@@ -14,7 +14,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
14
14
  *
15
15
  * MARC record validators used in Melinda
16
16
  *
17
- * Copyright (c) 2014-2020 University Of Helsinki (The National Library Of Finland)
17
+ * Copyright (c) 2014-2022 University Of Helsinki (The National Library Of Finland)
18
18
  *
19
19
  * This file is part of marc-record-validators-melinda
20
20
  *
@@ -98,6 +98,15 @@ describe('isbn-issn', () => {
98
98
  code: 'a',
99
99
  value: 'foo'
100
100
  }]
101
+ }, {
102
+ tag: '020',
103
+ ind1: ' ',
104
+ ind2: ' ',
105
+ subfields: [{
106
+ code: 'a',
107
+ value: '90-68-31-372-X'
108
+ }] // contains an extra hyphen
109
+
101
110
  }, {
102
111
  tag: '022',
103
112
  ind1: ' ',
@@ -151,18 +160,10 @@ describe('isbn-issn', () => {
151
160
  messages: ['ISSN (undefined) is not valid']
152
161
  });
153
162
  });
154
- it('Finds the record invalid (Spaces in ISBN)', async () => {
163
+ it('Finds the record invalid (reason: multiword)', async () => {
155
164
  const validator = await (0, _isbnIssn.default)();
156
165
  const record = new _marcRecord.MarcRecord({
157
166
  fields: [{
158
- tag: '020',
159
- ind1: ' ',
160
- ind2: ' ',
161
- subfields: [{
162
- code: 'a',
163
- value: ' 9789519155470'
164
- }]
165
- }, {
166
167
  tag: '020',
167
168
  ind1: ' ',
168
169
  ind2: ' ',
@@ -175,7 +176,7 @@ describe('isbn-issn', () => {
175
176
  const result = await validator.validate(record);
176
177
  (0, _chai.expect)(result).to.eql({
177
178
  valid: false,
178
- messages: ['ISBN ( 9789519155470) is not valid', 'ISBN (978-600-377-017-1 (nid.)) is not valid']
179
+ messages: ['ISBN (978-600-377-017-1 (nid.)) is not valid']
179
180
  });
180
181
  });
181
182
  it('Finds the record invalid (ISSN in \'l\'-subfield)', async () => {
@@ -218,6 +219,15 @@ describe('isbn-issn', () => {
218
219
  code: 'a',
219
220
  value: '9789519155470'
220
221
  }]
222
+ }, {
223
+ tag: '020',
224
+ ind1: ' ',
225
+ ind2: ' ',
226
+ subfields: [{
227
+ code: 'a',
228
+ value: '9068-31-372-X'
229
+ }] // legal digits, but bad hyphenation
230
+
221
231
  }, {
222
232
  tag: '020',
223
233
  ind1: ' ',
@@ -231,7 +241,7 @@ describe('isbn-issn', () => {
231
241
  const result = await validator.validate(record);
232
242
  (0, _chai.expect)(result).to.eql({
233
243
  valid: false,
234
- messages: ['ISBN (9789519155470) is not valid', 'ISBN (386006004X) is not valid']
244
+ messages: ['ISBN (9789519155470) is not valid', 'ISBN (9068-31-372-X) is not valid', 'ISBN (386006004X) is not valid']
235
245
  });
236
246
  });
237
247
  it.skip('Finds the record invalid (Missing ISBN)');
@@ -382,18 +392,19 @@ describe('isbn-issn', () => {
382
392
  ind2: ' ',
383
393
  subfields: [{
384
394
  code: 'a',
385
- value: '9786003770171 (nid.)'
395
+ value: '9786003770171 (nidottu)'
386
396
  }]
387
397
  }]
388
398
  });
389
399
  await validator.fix(record);
390
- (0, _chai.expect)(record.fields).to.eql([{
400
+ (0, _chai.expect)(record.fields).to.eql([// NB! Initial space does not need to be removed. It's crap, but not this fixer's crap.
401
+ {
391
402
  tag: '020',
392
403
  ind1: ' ',
393
404
  ind2: ' ',
394
405
  subfields: [{
395
406
  code: 'a',
396
- value: '9786003770171'
407
+ value: ' 9786003770171'
397
408
  }]
398
409
  }, {
399
410
  tag: '020',
@@ -402,6 +413,9 @@ describe('isbn-issn', () => {
402
413
  subfields: [{
403
414
  code: 'a',
404
415
  value: '9786003770171'
416
+ }, {
417
+ code: 'q',
418
+ value: '(nidottu)'
405
419
  }]
406
420
  }]);
407
421
  });
@@ -440,6 +454,10 @@ describe('isbn-issn', () => {
440
454
  }, {
441
455
  code: 'z',
442
456
  value: 'foo bar'
457
+ }, // NB! Technically $q should come before $z subfields, but this is good enough.
458
+ {
459
+ code: 'q',
460
+ value: '(nid.)'
443
461
  }]
444
462
  }]);
445
463
  });
@@ -451,6 +469,14 @@ describe('isbn-issn', () => {
451
469
  fields: [{
452
470
  tag: '005',
453
471
  value: 'whatever'
472
+ }, {
473
+ tag: '020',
474
+ ind1: ' ',
475
+ ind2: ' ',
476
+ subfields: [{
477
+ code: 'q',
478
+ value: 'sidottu'
479
+ }]
454
480
  }, {
455
481
  tag: '024',
456
482
  ind1: ' ',
@@ -465,6 +491,14 @@ describe('isbn-issn', () => {
465
491
  (0, _chai.expect)(record.fields).to.eql([{
466
492
  tag: '005',
467
493
  value: 'whatever'
494
+ }, {
495
+ tag: '020',
496
+ ind1: ' ',
497
+ ind2: ' ',
498
+ subfields: [{
499
+ code: 'q',
500
+ value: 'sidottu'
501
+ }]
468
502
  }, {
469
503
  tag: '024',
470
504
  ind1: ' ',
@@ -496,6 +530,15 @@ describe('isbn-issn', () => {
496
530
  code: 'a',
497
531
  value: '917153086X'
498
532
  }]
533
+ }, {
534
+ tag: '020',
535
+ ind1: ' ',
536
+ ind2: ' ',
537
+ subfields: [{
538
+ code: 'a',
539
+ value: '9068-31-372-X'
540
+ }] // legal digits, but bad hyphenation
541
+
499
542
  }, {
500
543
  tag: '020',
501
544
  ind1: ' ',
@@ -540,12 +583,24 @@ describe('isbn-issn', () => {
540
583
  value: '91-7153-086-X'
541
584
  }]
542
585
  }, {
586
+ tag: '020',
587
+ ind1: ' ',
588
+ ind2: ' ',
589
+ subfields: [{
590
+ code: 'a',
591
+ value: '90-6831-372-X'
592
+ }]
593
+ }, // corrected hyphens
594
+ {
543
595
  tag: '020',
544
596
  ind1: ' ',
545
597
  ind2: ' ',
546
598
  subfields: [{
547
599
  code: 'a',
548
600
  value: '3-86006-004-X'
601
+ }, {
602
+ code: 'q',
603
+ value: '(nid.)'
549
604
  }]
550
605
  }, {
551
606
  tag: '020',