@natlibfi/marc-record-validators-melinda 13.0.2 → 13.0.3
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/cyrillux-usemarcon-replacement.js +2 -3
- package/dist/cyrillux-usemarcon-replacement.js.map +2 -2
- package/dist/cyrillux.js +4 -5
- package/dist/cyrillux.js.map +2 -2
- package/dist/disambiguateSeriesStatements.js +1 -3
- package/dist/disambiguateSeriesStatements.js.map +2 -2
- package/dist/drop-terms.js +1 -2
- package/dist/drop-terms.js.map +2 -2
- package/dist/field-008-18-34-character-groups.js +1 -2
- package/dist/field-008-18-34-character-groups.js.map +2 -2
- package/dist/field-505-separators.js +1 -2
- package/dist/field-505-separators.js.map +2 -2
- package/dist/field-521-fix.js +1 -2
- package/dist/field-521-fix.js.map +2 -2
- package/dist/fix-33X.js +1 -2
- package/dist/fix-33X.js.map +2 -2
- package/dist/fix-language-codes.js +1 -2
- package/dist/fix-language-codes.js.map +2 -2
- package/dist/fix-sami-041.js +2 -3
- package/dist/fix-sami-041.js.map +2 -2
- package/dist/fixRelatorTerms.js +1 -2
- package/dist/fixRelatorTerms.js.map +2 -2
- package/dist/identical-fields.js +2 -2
- package/dist/identical-fields.js.map +2 -2
- package/dist/mergeRelatorTermFields.js +3 -4
- package/dist/mergeRelatorTermFields.js.map +2 -2
- package/dist/modernize-502.js +1 -2
- package/dist/modernize-502.js.map +2 -2
- package/dist/multiple-subfield-0.js.map +2 -2
- package/dist/normalize-dashes.js +1 -2
- package/dist/normalize-dashes.js.map +2 -2
- package/dist/normalize-identifiers.js +1 -2
- package/dist/normalize-identifiers.js.map +2 -2
- package/dist/normalize-qualifying-information.js +1 -2
- package/dist/normalize-qualifying-information.js.map +2 -2
- package/dist/normalize-utf8-diacritics.js +1 -2
- package/dist/normalize-utf8-diacritics.js.map +2 -2
- package/dist/normalizeFieldForComparison.js +1 -2
- package/dist/normalizeFieldForComparison.js.map +2 -2
- package/dist/punctuation/index.js +3 -8
- package/dist/punctuation/index.js.map +2 -2
- package/dist/punctuation2.js +1 -2
- package/dist/punctuation2.js.map +2 -2
- package/dist/remove-041-zxx.js +1 -2
- package/dist/remove-041-zxx.js.map +2 -2
- package/dist/sanitize-vocabulary-source-codes.js +1 -2
- package/dist/sanitize-vocabulary-source-codes.js.map +2 -2
- package/dist/sortFields.js +1 -2
- package/dist/sortFields.js.map +2 -2
- package/dist/sortRelatorTerms.js +1 -2
- package/dist/sortRelatorTerms.js.map +2 -2
- package/dist/sortSubfields.js +1 -2
- package/dist/sortSubfields.js.map +2 -2
- package/dist/subfieldValueNormalizations.js +1 -2
- package/dist/subfieldValueNormalizations.js.map +2 -2
- package/dist/sync-007-and-300.js +1 -2
- package/dist/sync-007-and-300.js.map +2 -2
- package/dist/sync-language.js +2 -3
- package/dist/sync-language.js.map +2 -2
- package/dist/translate-terms.js +1 -2
- package/dist/translate-terms.js.map +2 -2
- package/package.json +2 -2
- package/src/cyrillux-usemarcon-replacement.js +3 -3
- package/src/cyrillux.js +5 -5
- package/src/disambiguateSeriesStatements.js +2 -3
- package/src/drop-terms.js +2 -2
- package/src/field-008-18-34-character-groups.js +2 -2
- package/src/field-505-separators.js +2 -2
- package/src/field-521-fix.js +2 -2
- package/src/fix-33X.js +2 -2
- package/src/fix-language-codes.js +2 -2
- package/src/fix-sami-041.js +3 -3
- package/src/fixRelatorTerms.js +2 -2
- package/src/identical-fields.js +2 -3
- package/src/mergeRelatorTermFields.js +4 -4
- package/src/modernize-502.js +2 -2
- package/src/multiple-subfield-0.js +0 -1
- package/src/normalize-dashes.js +2 -2
- package/src/normalize-identifiers.js +2 -2
- package/src/normalize-qualifying-information.js +1 -2
- package/src/normalize-utf8-diacritics.js +2 -2
- package/src/normalizeFieldForComparison.js +3 -3
- package/src/punctuation/index.js +4 -9
- package/src/punctuation2.js +2 -2
- package/src/remove-041-zxx.js +2 -2
- package/src/sanitize-vocabulary-source-codes.js +2 -2
- package/src/sortFields.js +2 -2
- package/src/sortRelatorTerms.js +2 -2
- package/src/sortSubfields.js +2 -2
- package/src/subfieldValueNormalizations.js +2 -2
- package/src/sync-007-and-300.js +2 -2
- package/src/sync-language.js +2 -3
- package/src/translate-terms.js +1 -3
- package/dist/sort-tags.js +0 -41
- package/dist/sort-tags.js.map +0 -7
- package/dist/sort-tags.test.js +0 -261
- package/dist/sort-tags.test.js.map +0 -7
- package/src/sort-tags.js +0 -48
- package/src/sort-tags.test.js +0 -265
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/sortSubfields.js"],
|
|
4
|
-
"sourcesContent": ["// Author(s): Nicholas Volk\n\nimport clone from 'clone';\nimport createDebugLogger from 'debug';\nimport {fieldToString, nvdebug} from './utils.js';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sortSubfields');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst defaultSortOrderStringFinns = '863abcdefghijklmnopqrstuvwxyz4201759'; // NB! We Finns like $2 before $0 in 6XX...\nconst defaultSortOrderStringOthers = '863abcdefghijklmnopqrstuvwxyz4021759'; // NB 760-788 have '7' in different position...\n\nconst defaultSortOrderFinns = defaultSortOrderStringFinns.split('');\nconst defaultSortOrderOthers = defaultSortOrderStringOthers.split('');\n\n\nexport default function (defaultTagPattern) {\n\n return {\n description: 'Swap adjacent subfields',\n validate, fix\n };\n\n function getRelevantFields(record, tagPattern) {\n const datafields = record.fields.filter(f => f.subfields);\n if (!tagPattern) {\n return datafields;\n }\n\n const regexp = new RegExp(tagPattern, 'u');\n return datafields.filter(f => regexp.test(f.tag));\n }\n\n function fix(record, tagPattern = defaultTagPattern) {\n const res = {message: [], fix: [], valid: true};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n sortAdjacentSubfields(field);\n });\n\n return res;\n }\n\n function validate(record, tagPattern = defaultTagPattern) {\n const res = {message: []};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n const clonedField = clone(field);\n sortAdjacentSubfields(clonedField);\n const clonedFieldAsString = fieldToString(clonedField);\n const fieldAsString = fieldToString(field);\n if (fieldAsString !== clonedFieldAsString) {\n res.message.push(clonedFieldAsString);\n }\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n}\n\n\n// X00, X10, X11 and X130 could also for their own sets...\n// (ouch! sometimes $c comes after $d...): LoC: 100 0# \u2021a Black Foot, \u2021c Chief, \u2021d d. 1877 \u2021c (Spirit)\nconst sortOrderForX00 = ['i', 'a', 'b', 'q', 'c', 'd', 'e', 't', 'u', 'l', 'f', 'x', 'y', 'z', '0', '1', '5', '9']; // skip $g. Can't remember why, though...\nconst sortOrderForX10 = ['i', 'a', 'b', 't', 'n', 'c', 'e', 'v', 'w', 'x', 'y', 'z', '0', '1', '5', '9']; // somewhat iffy\nconst sortOrderForX11 = ['a', 'n', 'd', 'c', 'e', 'g', 'j', '0', '1', '5', '9'];\nconst sortOrderFor7XXLinkingEntries = ['8', '7', 'i', 'a', 's', 't', 'b', 'c', 'd', 'm', 'h', 'k', 'o', 'x', 'z', 'g', 'q', 'w', '0', '1', 'l', '9', '5'];\nconst sortOrderFor246 = ['i', 'a', 'b', 'n', 'p', 'f', '5', '9']; // Used by field 946 as well\n\n// List *only* exceptional order here. Otherwise default order is used.\nconst subfieldSortOrder = [\n {'tag': '017', 'sortOrder': ['i', 'a', 'b', 'd']},\n {'tag': '028', 'sortOrder': ['b', 'a', 'q']}, // National convention\n //{'tag': '031', 'sortOrder': ['a', 'b', 'c', 'm', 'e', 'd']}, // utter guesswork\n {'tag': '040', 'sortOrder': ['8', 'a', 'b', 'e', 'c', 'd', 'x']},\n // rda mus: 041: k < h < e < n < g < m < j (I think h<e is bad)\n // rda: g < h, j < p < h, i < j, k < h\n // loc: b < j, $m follows $b or $g\n // NV: $a \n {'tag': '041', 'sortOrder': ['8', 'a', 'd', 'e', 'b', 'f', 'i', 'j', 'p', 'k', 'h', 'n', 'g', 'm']}, // guesswork\n {'tag': '048', 'sortOrder': ['8', 'b', 'a']},\n {'tag': '100', 'sortOrder': sortOrderForX00},\n {'tag': '110', 'sortOrder': sortOrderForX10},\n {'tag': '111', 'sortOrder': sortOrderForX11},\n {'tag': '130', 'sortOrder': ['a', 'n', 'p', 'k', 'l']},\n {'tag': '240', 'sortOrder': ['a', 'm', 'n', 'p', 's', 'l', '2', '0', '1', '5', '9']},\n {'tag': '245', 'sortOrder': ['a', 'b', 'n', 'p', 'k', 'f', 'c']},\n {'tag': '246', 'sortOrder': sortOrderFor246},\n {'tag': '382', 'sortOrder': ['a']},\n {'tag': '385', 'sortOrder': ['8', 'm', 'n', 'a', '2', '0', '1']},\n {'tag': '386', 'sortOrder': ['8', 'm', 'n', 'a']},\n {'tag': '490', 'sortOrder': ['a', 'x', 'y', 'v', 'l']},\n {'tag': '505', 'sortOrder': ['a']},\n {'tag': '526', 'sortOrder': ['i', 'a', 'b', 'x', 'z']},\n {'tag': '534', 'sortOrder': ['3', 'p', 'a', 't', 'l', 'c', 'f', 'b', 'e', 'o', 'x', 'z']},\n {'tag': '540', 'sortOrder': ['a', 'b', 'c', 'd', 'f', '2', 'u']},\n {'tag': '600', 'sortOrder': sortOrderForX00},\n {'tag': '610', 'sortOrder': sortOrderForX10},\n {'tag': '611', 'sortOrder': sortOrderForX11},\n {'tag': '650', 'sortOrder': ['a', 'x', 'y', 'z']},\n {'tag': '700', 'sortOrder': sortOrderForX00},\n {'tag': '710', 'sortOrder': sortOrderForX10},\n {'tag': '711', 'sortOrder': sortOrderForX11},\n {'tag': '760', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '762', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '765', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '767', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '770', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '772', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '773', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '774', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '775', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '776', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '777', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '780', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '785', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '786', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '787', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '788', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '800', 'sortOrder': sortOrderForX00},\n {'tag': '810', 'sortOrder': sortOrderForX10},\n {'tag': '811', 'sortOrder': sortOrderForX11},\n {'tag': '830', 'sortOrder': ['a', 'n', 'x', 'v']}, // INCOMPLETE, SAME AS 490? APPARENTLY NOT...\n {'tag': '856', 'sortOrder': ['3', 'u', 'q', 'x', 'y', 'z', '5']}, // incomplete, LoC examples are inconclusive\n {'tag': '880', 'sortOrder': ['a']},\n {'tag': '946', 'sortOrder': sortOrderFor246},\n {'tag': 'LOW', 'sortOrder': ['a', 'b', 'c', 'l', 'h']},\n {'tag': 'SID', 'sortOrder': ['c', 'b']} // Hack, so that default order is not used\n];\n\nfunction getSubfieldSortOrder(field) {\n const entry = subfieldSortOrder.filter(currEntry => field.tag === currEntry.tag);\n if (entry.length > 0 && 'sortOrder' in entry[0]) {\n debugDev(`sort order for ${field.tag}: ${entry[0].sortOrder}`);\n return entry[0].sortOrder;\n }\n if (!['300'].includes(field.tag)) { // Lis tags which use normal $a...$z order here!\n nvdebug(`WARNING!\\tNo subfield order found for ${field.tag}.`, debugDev);\n }\n return [];\n}\n\n\nfunction swapSubfields(field, sortOrder) {\n if (!field.subfields) {\n return;\n }\n\n const loopAgain = field.subfields.some((sf, index) => {\n if (index === 0) {\n return false;\n }\n const currPos = getPosition(sf, sortOrder);\n const prevPos = getPosition(field.subfields[index - 1], sortOrder);\n if (currPos === -1 || prevPos === -1 || currPos >= prevPos) {\n return false;\n }\n // Swap:\n const tmp = field.subfields[index - 1];\n field.subfields[index - 1] = sf;\n field.subfields[index] = tmp;\n return true;\n });\n\n if (loopAgain) {\n return swapSubfields(field, sortOrder);\n }\n\n return;\n\n function getPosition(subfield, sortOrder) {\n // Magic exception that *always* comes first, used by Aleph in linking overlong fields\n if (sortOrder.indexOf('9') > -1 && subfield.code === '9' && ['^', '^^'].includes(subfield.value)) {\n return -0.5; // normal \"best value\" is 0, and \"worst value\" is N\n }\n return sortOrder.indexOf(subfield.code);\n }\n}\n\n\nfunction twoBeforeZero(field) {\n const sf2 = field.subfields.filter(sf => sf.code === '2');\n if (sf2.length !== 1) {\n return true; // both true and false are ok here\n }\n // MRA-465: gcipplatform (field 753)\n // rdasco (344), creatorbio (353), gbd (668), lsch (eg. 385)\n if (['creatorbio', 'gbd', 'gcipplatform', 'lscsh', 'rdasco'].includes(sf2[0].value)) {\n return false;\n }\n return true;\n}\n\nfunction moveSubfield6ToTheFront(field) {\n // https://www.loc.gov/marc/bibliographic/ecbdcntf.html says \"Subfield $6 is always the first subfield in the field.\"\n const sf6s = field.subfields.filter(sf => sf.code === '6');\n const others = field.subfields.filter(sf => sf.code !== '6');\n field.subfields = [...sf6s, ...others];\n}\n\nexport function sortAdjacentSubfields(field, externalSortOrder = []) {\n if (!field.subfields) {\n return field;\n }\n\n moveSubfield6ToTheFront(field); // specs: \"Subfield $6 is always the first subfield in the field.\"\n\n // Features:\n // - Swap only sort adjacent pairs.\n // - No sorting over unlisted subfield codes. Thus a given subfield can not shift to wrong side of 700$t...\n\n // Implement: 880 field should use values from $6...\n\n // Should we support multiple sort orders per field?\n\n // Try to handle control subfield order. This is not 100% fool proof. Control subfields are pretty stable, though.\n // However, there are exceptions (eg. $9 ^^ comes first and $2 $0 is a Finnish convention...)\n\n\n const finnishWay = twoBeforeZero(field);\n\n // Note: 760-789: '7' comes way earlier (after '6' and '8')\n const controlSubfieldOrder = finnishWay ? ['8', '3', 'a', '4', '2', '0', '1', '7', '5', '9'] : ['8', '7', '3', 'a', '4', '0', '1', '2', '7', '5', '9'];\n swapSubfields(field, controlSubfieldOrder);\n\n const sortOrderForField = externalSortOrder.length > 0 ? externalSortOrder : getSubfieldSortOrder(field);\n //nvdebug(`INTERMEDIATE SUBFIELD ORDER FOR ${field.tag}: ${sortOrderForField.join(', ')}`, debugDev);\n\n const defaultSortOrder = finnishWay ? defaultSortOrderFinns : defaultSortOrderOthers; // $2 vs $0\n const subfieldOrder = sortOrderForField.length > 0 ? sortOrderForField : defaultSortOrder;\n //nvdebug(`FINAL SUBFIELD ORDER (FINNISH=${finnishWay}) FOR ${field.tag}: ${subfieldOrder.join(', ')}`, debugDev);\n //if (sortOrder === null) { return field; } //// Currently always sort..\n //nvdebug(`IN: ${fieldToString(field)}`, debugDev);\n swapSubfields(field, subfieldOrder);\n //nvdebug(`OUT: ${fieldToString(field)}`, debugDev);\n\n return field;\n}\n\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["// Author(s): Nicholas Volk\n\n\nimport createDebugLogger from 'debug';\nimport {fieldToString, nvdebug} from './utils.js';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sortSubfields');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst defaultSortOrderStringFinns = '863abcdefghijklmnopqrstuvwxyz4201759'; // NB! We Finns like $2 before $0 in 6XX...\nconst defaultSortOrderStringOthers = '863abcdefghijklmnopqrstuvwxyz4021759'; // NB 760-788 have '7' in different position...\n\nconst defaultSortOrderFinns = defaultSortOrderStringFinns.split('');\nconst defaultSortOrderOthers = defaultSortOrderStringOthers.split('');\n\n\nexport default function (defaultTagPattern) {\n\n return {\n description: 'Swap adjacent subfields',\n validate, fix\n };\n\n function getRelevantFields(record, tagPattern) {\n const datafields = record.fields.filter(f => f.subfields);\n if (!tagPattern) {\n return datafields;\n }\n\n const regexp = new RegExp(tagPattern, 'u');\n return datafields.filter(f => regexp.test(f.tag));\n }\n\n function fix(record, tagPattern = defaultTagPattern) {\n const res = {message: [], fix: [], valid: true};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n sortAdjacentSubfields(field);\n });\n\n return res;\n }\n\n function validate(record, tagPattern = defaultTagPattern) {\n const res = {message: []};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n const clonedField = structuredClone(field);\n sortAdjacentSubfields(clonedField);\n const clonedFieldAsString = fieldToString(clonedField);\n const fieldAsString = fieldToString(field);\n if (fieldAsString !== clonedFieldAsString) {\n res.message.push(clonedFieldAsString);\n }\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n}\n\n\n// X00, X10, X11 and X130 could also for their own sets...\n// (ouch! sometimes $c comes after $d...): LoC: 100 0# \u2021a Black Foot, \u2021c Chief, \u2021d d. 1877 \u2021c (Spirit)\nconst sortOrderForX00 = ['i', 'a', 'b', 'q', 'c', 'd', 'e', 't', 'u', 'l', 'f', 'x', 'y', 'z', '0', '1', '5', '9']; // skip $g. Can't remember why, though...\nconst sortOrderForX10 = ['i', 'a', 'b', 't', 'n', 'c', 'e', 'v', 'w', 'x', 'y', 'z', '0', '1', '5', '9']; // somewhat iffy\nconst sortOrderForX11 = ['a', 'n', 'd', 'c', 'e', 'g', 'j', '0', '1', '5', '9'];\nconst sortOrderFor7XXLinkingEntries = ['8', '7', 'i', 'a', 's', 't', 'b', 'c', 'd', 'm', 'h', 'k', 'o', 'x', 'z', 'g', 'q', 'w', '0', '1', 'l', '9', '5'];\nconst sortOrderFor246 = ['i', 'a', 'b', 'n', 'p', 'f', '5', '9']; // Used by field 946 as well\n\n// List *only* exceptional order here. Otherwise default order is used.\nconst subfieldSortOrder = [\n {'tag': '017', 'sortOrder': ['i', 'a', 'b', 'd']},\n {'tag': '028', 'sortOrder': ['b', 'a', 'q']}, // National convention\n //{'tag': '031', 'sortOrder': ['a', 'b', 'c', 'm', 'e', 'd']}, // utter guesswork\n {'tag': '040', 'sortOrder': ['8', 'a', 'b', 'e', 'c', 'd', 'x']},\n // rda mus: 041: k < h < e < n < g < m < j (I think h<e is bad)\n // rda: g < h, j < p < h, i < j, k < h\n // loc: b < j, $m follows $b or $g\n // NV: $a \n {'tag': '041', 'sortOrder': ['8', 'a', 'd', 'e', 'b', 'f', 'i', 'j', 'p', 'k', 'h', 'n', 'g', 'm']}, // guesswork\n {'tag': '048', 'sortOrder': ['8', 'b', 'a']},\n {'tag': '100', 'sortOrder': sortOrderForX00},\n {'tag': '110', 'sortOrder': sortOrderForX10},\n {'tag': '111', 'sortOrder': sortOrderForX11},\n {'tag': '130', 'sortOrder': ['a', 'n', 'p', 'k', 'l']},\n {'tag': '240', 'sortOrder': ['a', 'm', 'n', 'p', 's', 'l', '2', '0', '1', '5', '9']},\n {'tag': '245', 'sortOrder': ['a', 'b', 'n', 'p', 'k', 'f', 'c']},\n {'tag': '246', 'sortOrder': sortOrderFor246},\n {'tag': '382', 'sortOrder': ['a']},\n {'tag': '385', 'sortOrder': ['8', 'm', 'n', 'a', '2', '0', '1']},\n {'tag': '386', 'sortOrder': ['8', 'm', 'n', 'a']},\n {'tag': '490', 'sortOrder': ['a', 'x', 'y', 'v', 'l']},\n {'tag': '505', 'sortOrder': ['a']},\n {'tag': '526', 'sortOrder': ['i', 'a', 'b', 'x', 'z']},\n {'tag': '534', 'sortOrder': ['3', 'p', 'a', 't', 'l', 'c', 'f', 'b', 'e', 'o', 'x', 'z']},\n {'tag': '540', 'sortOrder': ['a', 'b', 'c', 'd', 'f', '2', 'u']},\n {'tag': '600', 'sortOrder': sortOrderForX00},\n {'tag': '610', 'sortOrder': sortOrderForX10},\n {'tag': '611', 'sortOrder': sortOrderForX11},\n {'tag': '650', 'sortOrder': ['a', 'x', 'y', 'z']},\n {'tag': '700', 'sortOrder': sortOrderForX00},\n {'tag': '710', 'sortOrder': sortOrderForX10},\n {'tag': '711', 'sortOrder': sortOrderForX11},\n {'tag': '760', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '762', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '765', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '767', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '770', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '772', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '773', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '774', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '775', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '776', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '777', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '780', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '785', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '786', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '787', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '788', 'sortOrder': sortOrderFor7XXLinkingEntries},\n {'tag': '800', 'sortOrder': sortOrderForX00},\n {'tag': '810', 'sortOrder': sortOrderForX10},\n {'tag': '811', 'sortOrder': sortOrderForX11},\n {'tag': '830', 'sortOrder': ['a', 'n', 'x', 'v']}, // INCOMPLETE, SAME AS 490? APPARENTLY NOT...\n {'tag': '856', 'sortOrder': ['3', 'u', 'q', 'x', 'y', 'z', '5']}, // incomplete, LoC examples are inconclusive\n {'tag': '880', 'sortOrder': ['a']},\n {'tag': '946', 'sortOrder': sortOrderFor246},\n {'tag': 'LOW', 'sortOrder': ['a', 'b', 'c', 'l', 'h']},\n {'tag': 'SID', 'sortOrder': ['c', 'b']} // Hack, so that default order is not used\n];\n\nfunction getSubfieldSortOrder(field) {\n const entry = subfieldSortOrder.filter(currEntry => field.tag === currEntry.tag);\n if (entry.length > 0 && 'sortOrder' in entry[0]) {\n debugDev(`sort order for ${field.tag}: ${entry[0].sortOrder}`);\n return entry[0].sortOrder;\n }\n if (!['300'].includes(field.tag)) { // Lis tags which use normal $a...$z order here!\n nvdebug(`WARNING!\\tNo subfield order found for ${field.tag}.`, debugDev);\n }\n return [];\n}\n\n\nfunction swapSubfields(field, sortOrder) {\n if (!field.subfields) {\n return;\n }\n\n const loopAgain = field.subfields.some((sf, index) => {\n if (index === 0) {\n return false;\n }\n const currPos = getPosition(sf, sortOrder);\n const prevPos = getPosition(field.subfields[index - 1], sortOrder);\n if (currPos === -1 || prevPos === -1 || currPos >= prevPos) {\n return false;\n }\n // Swap:\n const tmp = field.subfields[index - 1];\n field.subfields[index - 1] = sf;\n field.subfields[index] = tmp;\n return true;\n });\n\n if (loopAgain) {\n return swapSubfields(field, sortOrder);\n }\n\n return;\n\n function getPosition(subfield, sortOrder) {\n // Magic exception that *always* comes first, used by Aleph in linking overlong fields\n if (sortOrder.indexOf('9') > -1 && subfield.code === '9' && ['^', '^^'].includes(subfield.value)) {\n return -0.5; // normal \"best value\" is 0, and \"worst value\" is N\n }\n return sortOrder.indexOf(subfield.code);\n }\n}\n\n\nfunction twoBeforeZero(field) {\n const sf2 = field.subfields.filter(sf => sf.code === '2');\n if (sf2.length !== 1) {\n return true; // both true and false are ok here\n }\n // MRA-465: gcipplatform (field 753)\n // rdasco (344), creatorbio (353), gbd (668), lsch (eg. 385)\n if (['creatorbio', 'gbd', 'gcipplatform', 'lscsh', 'rdasco'].includes(sf2[0].value)) {\n return false;\n }\n return true;\n}\n\nfunction moveSubfield6ToTheFront(field) {\n // https://www.loc.gov/marc/bibliographic/ecbdcntf.html says \"Subfield $6 is always the first subfield in the field.\"\n const sf6s = field.subfields.filter(sf => sf.code === '6');\n const others = field.subfields.filter(sf => sf.code !== '6');\n field.subfields = [...sf6s, ...others];\n}\n\nexport function sortAdjacentSubfields(field, externalSortOrder = []) {\n if (!field.subfields) {\n return field;\n }\n\n moveSubfield6ToTheFront(field); // specs: \"Subfield $6 is always the first subfield in the field.\"\n\n // Features:\n // - Swap only sort adjacent pairs.\n // - No sorting over unlisted subfield codes. Thus a given subfield can not shift to wrong side of 700$t...\n\n // Implement: 880 field should use values from $6...\n\n // Should we support multiple sort orders per field?\n\n // Try to handle control subfield order. This is not 100% fool proof. Control subfields are pretty stable, though.\n // However, there are exceptions (eg. $9 ^^ comes first and $2 $0 is a Finnish convention...)\n\n\n const finnishWay = twoBeforeZero(field);\n\n // Note: 760-789: '7' comes way earlier (after '6' and '8')\n const controlSubfieldOrder = finnishWay ? ['8', '3', 'a', '4', '2', '0', '1', '7', '5', '9'] : ['8', '7', '3', 'a', '4', '0', '1', '2', '7', '5', '9'];\n swapSubfields(field, controlSubfieldOrder);\n\n const sortOrderForField = externalSortOrder.length > 0 ? externalSortOrder : getSubfieldSortOrder(field);\n //nvdebug(`INTERMEDIATE SUBFIELD ORDER FOR ${field.tag}: ${sortOrderForField.join(', ')}`, debugDev);\n\n const defaultSortOrder = finnishWay ? defaultSortOrderFinns : defaultSortOrderOthers; // $2 vs $0\n const subfieldOrder = sortOrderForField.length > 0 ? sortOrderForField : defaultSortOrder;\n //nvdebug(`FINAL SUBFIELD ORDER (FINNISH=${finnishWay}) FOR ${field.tag}: ${subfieldOrder.join(', ')}`, debugDev);\n //if (sortOrder === null) { return field; } //// Currently always sort..\n //nvdebug(`IN: ${fieldToString(field)}`, debugDev);\n swapSubfields(field, subfieldOrder);\n //nvdebug(`OUT: ${fieldToString(field)}`, debugDev);\n\n return field;\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["sortOrder"]
|
|
7
7
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import { fieldHasSubfield, fieldToString, getCatalogingLanguage } from "./utils.js";
|
|
3
2
|
import { fieldFixPunctuation } from "./punctuation2.js";
|
|
4
3
|
import { fieldGetUnambiguousTag } from "./subfield6Utils.js";
|
|
@@ -30,7 +29,7 @@ export default function() {
|
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
const orig = fieldToString(field);
|
|
33
|
-
const normalizedField = normalizeSubfieldValues(
|
|
32
|
+
const normalizedField = normalizeSubfieldValues(structuredClone(field), catLang);
|
|
34
33
|
const mod = fieldToString(normalizedField);
|
|
35
34
|
if (orig !== mod) {
|
|
36
35
|
res.message.push(`'${orig}' requires subfield internal mods/normalization`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/subfieldValueNormalizations.js"],
|
|
4
|
-
"sourcesContent": ["//import createDebugLogger from 'debug';\nimport
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["//import createDebugLogger from 'debug';\n\nimport {fieldHasSubfield, fieldToString, getCatalogingLanguage} from './utils.js';\nimport {fieldFixPunctuation} from './punctuation2.js';\nimport {fieldGetUnambiguousTag} from './subfield6Utils.js';\n\n// NB! You should probably run punctuation fixes after this validator!\n\n// Author(s): Nicholas Volk\nexport default function () {\n\n return {\n description: 'Fix various subfield internal values',\n validate, fix\n };\n\n function fix(record) {\n const catLang = getCatalogingLanguage(record, 'fin');\n const res = {message: [], fix: [], valid: true};\n\n record.fields.forEach(field => {\n normalizeSubfieldValues(field, catLang);\n });\n\n // message.valid = !(message.message.length >= 1);\n return res;\n }\n\n function validate(record) {\n const catLang = getCatalogingLanguage(record, 'fin');\n const res = {message: []};\n\n record.fields.forEach(field => {\n validateField(field, res, catLang);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res, catLang) {\n if (!field.subfields) {\n return;\n }\n const orig = fieldToString(field);\n\n const normalizedField = normalizeSubfieldValues(structuredClone(field), catLang);\n const mod = fieldToString(normalizedField);\n if (orig !== mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n res.message.push(`'${orig}' requires subfield internal mods/normalization`);\n return;\n }\n return;\n }\n}\n\n\nfunction handleInitials(value, subfieldCode, field) {\n // MRA-267/273\n if (field.ind1 === '1' && subfieldCode === 'a' && ['100', '600', '700', '800'].includes(field.tag) && !fieldHasSubfield(field, '0')) {\n // Fix MRA-267/273 (partial): Handle the most common case(s). (And extend them rules later on if the need arises):\n // No longest initial sequence I've seen is six (in a Sri Lankan name).\n for (var i=0; i < 6 && initialsInRow(value); i++) {\n // NB: Regexp has ','. Everything before it belongs to the surname. Everything after it is free game.\n value = value.replace(/(,.*) ([A-Z]|\u00C5|\u00D6|\u00D6)\\.([A-Z]|\u00C5|\u00D6|\u00D6)/u, '$1 $2. $3');\n }\n }\n\n return value;\n\n function initialsInRow(str) {\n // initial space confirms us that it's an initial\n return str.match(/ (?:[A-Z]|\u00C5|\u00C4|\u00D6)\\.(?:[A-Z]|\u00C5|\u00C4|\u00D6)/u);\n }\n}\n\nfunction getNormalizedValue(subfield, field, catLang) {\n return handleFikt(uppercaseLanguage(handleMovies(handleInitials(subfield.value, subfield.code, field))));\n\n function handleFikt(value) {\n if (subfield.code !== 'c' || field.tag !== '600') {\n return value;\n }\n\n return wrapFiktiivinenHahmo(expandFikt(value));\n\n function wrapFiktiivinenHahmo(value) {\n if (value.includes('(')) { // Some kind of parentheses already found -> nothing to do here\n return value;\n }\n if (value.match(/^(?:fiktiivinen hahmo|fiktiv gestalt|fiktiivinen yhteis\u00F6)[,.]?$/u)) {\n // Hope that some other module handles punctuation, if needed:\n return `(${value.replace(/[.,]$/u, '')})`;\n }\n return value;\n }\n\n // wrap text around parentheses\n function expandFikt(value) {\n if (field.ind1 === '3') { // Not handling \"fiktiivinen yhteis\u00F6\" at the moment\n return value;\n }\n\n if (value.match(/\\bfikt\\.?(?:$|[,)])/u) && !value.match(/fikt.*fikt/ui)) {\n // NB! Dot '.' in 'fikt.' might also be punctuation as well. Run punctuation2 fixer after this fixer!\n if (catLang === 'fin') {\n return value.replace(/\\bfikt\\./u, 'fiktiivinen hahmo');\n }\n if (catLang === 'swe') {\n return value.replace(/\\bfikt\\./u, 'fiktiv gestalt');\n }\n }\n return value;\n }\n\n }\n\n function handleMovies(value) {\n if (subfield.code === 'a' && ['130', '630', '730'].includes(field.tag)) {\n // MRA-614: \"(elokuva, 2000)\" => \"(elokuva : 2000)\"\"\n return value.replace(/\\((elokuva), (19[0-9][0-9]|20[0-2][0-9])\\)/u, '($1 : $2)');\n }\n return value;\n }\n\n\n function uppercaseLanguage(value) { // Part of MET-549\n const relevantTags = ['130', '240', '243', '600', '610', '611', '630', '700', '710', '711', '730', '800', '810', '811', '830'];\n\n if (subfield.code !== 'l') {\n return value;\n }\n const targetTag = tagForUppercasing();\n if (relevantTags.includes(targetTag)) {\n const newValue = `${value[0].toUpperCase()}${value.slice(1)}`;\n if (newValue !== value) {\n fieldFixPunctuation(field); // Rather hackily try to fix prev punc on the fly\n return newValue;\n }\n }\n\n function tagForUppercasing() {\n return field.tag === '880' ? fieldGetUnambiguousTag(field) : field.tag;\n }\n\n return value;\n }\n}\n\nfunction normalizeSubfieldValues(field, catLang) {\n if (!field.subfields) {\n return field;\n }\n field.subfields.forEach((subfield, index) => {\n if (!field.subfields[index].value) {\n return;\n }\n field.subfields[index].value = getNormalizedValue(subfield, field, catLang);\n });\n return field;\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAQ,kBAAkB,eAAe,6BAA4B;AACrE,SAAQ,2BAA0B;AAClC,SAAQ,8BAA6B;AAKrC,0BAA2B;AAEzB,SAAO;AAAA,IACL,aAAa;AAAA,IACb;AAAA,IAAU;AAAA,EACZ;AAEA,WAAS,IAAI,QAAQ;AACnB,UAAM,UAAU,sBAAsB,QAAQ,KAAK;AACnD,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAE9C,WAAO,OAAO,QAAQ,WAAS;AAC7B,8BAAwB,OAAO,OAAO;AAAA,IACxC,CAAC;AAGD,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,UAAU,sBAAsB,QAAQ,KAAK;AACnD,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,WAAO,OAAO,QAAQ,WAAS;AAC7B,oBAAc,OAAO,KAAK,OAAO;AAAA,IACnC,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK,SAAS;AAC1C,QAAI,CAAC,MAAM,WAAW;AACpB;AAAA,IACF;AACA,UAAM,OAAO,cAAc,KAAK;AAEhC,UAAM,kBAAkB,wBAAwB,gBAAgB,KAAK,GAAG,OAAO;AAC/E,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB,UAAI,QAAQ,KAAK,IAAI,IAAI,iDAAiD;AAC1E;AAAA,IACF;AACA;AAAA,EACF;AACF;AAGA,SAAS,eAAe,OAAO,cAAc,OAAO;AAElD,MAAI,MAAM,SAAS,OAAO,iBAAiB,OAAO,CAAC,OAAO,OAAO,OAAO,KAAK,EAAE,SAAS,MAAM,GAAG,KAAK,CAAC,iBAAiB,OAAO,GAAG,GAAG;AAGnI,aAAS,IAAE,GAAG,IAAI,KAAK,cAAc,KAAK,GAAG,KAAK;AAEhD,cAAQ,MAAM,QAAQ,uCAAuC,WAAW;AAAA,IAC1E;AAAA,EACF;AAEA,SAAO;AAEP,WAAS,cAAc,KAAK;AAE1B,WAAO,IAAI,MAAM,oCAAoC;AAAA,EACvD;AACF;AAEA,SAAS,mBAAmB,UAAU,OAAO,SAAS;AACpD,SAAO,WAAW,kBAAkB,aAAa,eAAe,SAAS,OAAO,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC;AAEvG,WAAS,WAAW,OAAO;AACzB,QAAI,SAAS,SAAS,OAAO,MAAM,QAAQ,OAAO;AAChD,aAAO;AAAA,IACT;AAEA,WAAO,qBAAqB,WAAW,KAAK,CAAC;AAE7C,aAAS,qBAAqBA,QAAO;AACnC,UAAIA,OAAM,SAAS,GAAG,GAAG;AACvB,eAAOA;AAAA,MACT;AACA,UAAIA,OAAM,MAAM,kEAAkE,GAAG;AAEnF,eAAO,IAAIA,OAAM,QAAQ,UAAU,EAAE,CAAC;AAAA,MACxC;AACA,aAAOA;AAAA,IACT;AAGA,aAAS,WAAWA,QAAO;AACzB,UAAI,MAAM,SAAS,KAAK;AACtB,eAAOA;AAAA,MACT;AAEA,UAAIA,OAAM,MAAM,sBAAsB,KAAK,CAACA,OAAM,MAAM,cAAc,GAAG;AAEvE,YAAI,YAAY,OAAO;AACrB,iBAAOA,OAAM,QAAQ,aAAa,mBAAmB;AAAA,QACvD;AACA,YAAI,YAAY,OAAO;AACrB,iBAAOA,OAAM,QAAQ,aAAa,gBAAgB;AAAA,QACpD;AAAA,MACF;AACA,aAAOA;AAAA,IACT;AAAA,EAEF;AAEA,WAAS,aAAa,OAAO;AAC3B,QAAI,SAAS,SAAS,OAAO,CAAC,OAAO,OAAO,KAAK,EAAE,SAAS,MAAM,GAAG,GAAG;AAEtE,aAAO,MAAM,QAAQ,+CAA+C,WAAW;AAAA,IACjF;AACA,WAAO;AAAA,EACT;AAGA,WAAS,kBAAkB,OAAO;AAChC,UAAM,eAAe,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAE7H,QAAI,SAAS,SAAS,KAAK;AACzB,aAAO;AAAA,IACT;AACA,UAAM,YAAY,kBAAkB;AACpC,QAAI,aAAa,SAAS,SAAS,GAAG;AACpC,YAAM,WAAW,GAAG,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC;AAC3D,UAAI,aAAa,OAAO;AACtB,4BAAoB,KAAK;AACzB,eAAO;AAAA,MACT;AAAA,IACF;AAEA,aAAS,oBAAoB;AAC3B,aAAO,MAAM,QAAQ,QAAQ,uBAAuB,KAAK,IAAI,MAAM;AAAA,IACrE;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,wBAAwB,OAAO,SAAS;AAC/C,MAAI,CAAC,MAAM,WAAW;AACpB,WAAO;AAAA,EACT;AACA,QAAM,UAAU,QAAQ,CAAC,UAAU,UAAU;AAC3C,QAAI,CAAC,MAAM,UAAU,KAAK,EAAE,OAAO;AACjC;AAAA,IACF;AACA,UAAM,UAAU,KAAK,EAAE,QAAQ,mBAAmB,UAAU,OAAO,OAAO;AAAA,EAC5E,CAAC;AACD,SAAO;AACT;",
|
|
6
6
|
"names": ["value"]
|
|
7
7
|
}
|
package/dist/sync-007-and-300.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { fieldToString } from "./utils.js";
|
|
2
|
-
import clone from "clone";
|
|
3
2
|
export default function() {
|
|
4
3
|
return {
|
|
5
4
|
description: "Validator for updating mismatching f007 based on f300 (DVD/Bluray) (MRA-613)",
|
|
@@ -24,7 +23,7 @@ export default function() {
|
|
|
24
23
|
return;
|
|
25
24
|
}
|
|
26
25
|
const orig = fieldToString(field);
|
|
27
|
-
const normalizedField = fixField(
|
|
26
|
+
const normalizedField = fixField(structuredClone(field), record);
|
|
28
27
|
const mod = fieldToString(normalizedField);
|
|
29
28
|
if (orig !== mod) {
|
|
30
29
|
res.message.push(`FIXABLE: '${orig}' => '${mod}'`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/sync-007-and-300.js"],
|
|
4
|
-
"sourcesContent": ["//import createDebugLogger from 'debug';\nimport {fieldToString} from './utils.js';\
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["//import createDebugLogger from 'debug';\n\nimport {fieldToString} from './utils.js';\n\n//const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda/sanitize-vocabulary-source-codes);\n\n// Author(s): Nicholas Volk, Joni Ollila\nexport default function () {\n\n return {\n description: 'Validator for updating mismatching f007 based on f300 (DVD/Bluray) (MRA-613)',\n validate, fix\n };\n\n function fix(record) {\n const res = {message: [], fix: [], valid: true};\n record.fields.forEach(f => fixField(f, record));\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n record.fields.forEach(field => {\n validateField(field, res, record);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res, record) {\n if (field.tag !== '007') {\n return;\n }\n const orig = fieldToString(field);\n\n const normalizedField = fixField(structuredClone(field), record);\n const mod = fieldToString(normalizedField);\n if (orig !== mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n res.message.push(`FIXABLE: '${orig}' => '${mod}'`);\n return;\n }\n\n /*\n if (fieldHasUnfixableStuff(field)) {\n res.message.push(`CAN'T BE FIXED AUTOMATICALLY: '${orig}'`);\n return;\n }\n */\n return;\n }\n\n function fieldIsBluray007(field) {\n if (field.tag !== '007') {\n return false;\n }\n return field.value.match(/^v...s/u);\n }\n\n function fieldIsDvd007(field) {\n if (field.tag !== '007') {\n return false;\n }\n return field.value.match(/^v...v/u);\n }\n\n function fieldIsBluray300(field) {\n if (field.tag !== '300') {\n return false;\n }\n return field.subfields.some(subfield => subfield.value.match(/(?:Blu.?ray|BD)/ui));\n }\n\n function fieldIsDvd300(field) {\n if (field.tag !== '300') {\n return false;\n }\n return field.subfields.some(subfield => subfield.value.match(/DVD/ui));\n }\n\n function recordHasBluray300(record) {\n return record.fields.some(f => fieldIsBluray300(f));\n }\n\n function recordHasDvd300(record) {\n return record.fields.some(f => fieldIsDvd300(f));\n }\n\n function convert007BlurayToDvd(field) {\n if (!fieldIsBluray007(field)) {\n return;\n }\n //field.value = field.value.substring(0, 4) + 's' + field.value.substring(5);\n //field.value = field.value.replace(/^(?:v...)s/u, `${1}v`); // eslint-disable-line functional/immutable-data, no-template-curly-in-string\n field.value = `${field.value.substring(0, 4)}v${field.value.substring(5)}`;\n }\n\n function convert007DvdToBluray(field) {\n if (!fieldIsDvd007(field)) {\n return;\n }\n //field.value = field.value.replace(/^(?:v...)v/u, `${1}s`);\n field.value = `${field.value.substring(0, 4)}s${field.value.substring(5)}`;\n }\n\n function fixField(field, record) {\n if (field.tag !== '007') {\n return field;\n }\n\n if (fieldIsDvd007(field) && recordHasBluray300(record) && !recordHasDvd300(record)) {\n // FIX 007: DVD -> Blu-ray\n convert007DvdToBluray(field);\n return field;\n }\n\n if (fieldIsBluray007(field) && recordHasDvd300(record) && !recordHasBluray300(record)) {\n // FIX 007: Blu-Ray -> DVD\n convert007BlurayToDvd(field);\n return field;\n }\n\n return field;\n }\n\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAQ,qBAAoB;AAK5B,0BAA2B;AAEzB,SAAO;AAAA,IACL,aAAa;AAAA,IACb;AAAA,IAAU;AAAA,EACZ;AAEA,WAAS,IAAI,QAAQ;AACnB,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAC9C,WAAO,OAAO,QAAQ,OAAK,SAAS,GAAG,MAAM,CAAC;AAC9C,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,WAAO,OAAO,QAAQ,WAAS;AAC7B,oBAAc,OAAO,KAAK,MAAM;AAAA,IAClC,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK,QAAQ;AACzC,QAAI,MAAM,QAAQ,OAAO;AACvB;AAAA,IACF;AACA,UAAM,OAAO,cAAc,KAAK;AAEhC,UAAM,kBAAkB,SAAS,gBAAgB,KAAK,GAAG,MAAM;AAC/D,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB,UAAI,QAAQ,KAAK,aAAa,IAAI,SAAS,GAAG,GAAG;AACjD;AAAA,IACF;AAQA;AAAA,EACF;AAEA,WAAS,iBAAiB,OAAO;AAC/B,QAAI,MAAM,QAAQ,OAAO;AACvB,aAAO;AAAA,IACT;AACA,WAAO,MAAM,MAAM,MAAM,SAAS;AAAA,EACpC;AAEA,WAAS,cAAc,OAAO;AAC5B,QAAI,MAAM,QAAQ,OAAO;AACvB,aAAO;AAAA,IACT;AACA,WAAO,MAAM,MAAM,MAAM,SAAS;AAAA,EACpC;AAEA,WAAS,iBAAiB,OAAO;AAC/B,QAAI,MAAM,QAAQ,OAAO;AACvB,aAAO;AAAA,IACT;AACA,WAAO,MAAM,UAAU,KAAK,cAAY,SAAS,MAAM,MAAM,mBAAmB,CAAC;AAAA,EACnF;AAEA,WAAS,cAAc,OAAO;AAC5B,QAAI,MAAM,QAAQ,OAAO;AACvB,aAAO;AAAA,IACT;AACA,WAAO,MAAM,UAAU,KAAK,cAAY,SAAS,MAAM,MAAM,OAAO,CAAC;AAAA,EACvE;AAEA,WAAS,mBAAmB,QAAQ;AAClC,WAAO,OAAO,OAAO,KAAK,OAAK,iBAAiB,CAAC,CAAC;AAAA,EACpD;AAEA,WAAS,gBAAgB,QAAQ;AAC/B,WAAO,OAAO,OAAO,KAAK,OAAK,cAAc,CAAC,CAAC;AAAA,EACjD;AAEA,WAAS,sBAAsB,OAAO;AACpC,QAAI,CAAC,iBAAiB,KAAK,GAAG;AAC5B;AAAA,IACF;AAGA,UAAM,QAAQ,GAAG,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,MAAM,MAAM,UAAU,CAAC,CAAC;AAAA,EAC1E;AAEA,WAAS,sBAAsB,OAAO;AACpC,QAAI,CAAC,cAAc,KAAK,GAAG;AACzB;AAAA,IACF;AAEA,UAAM,QAAQ,GAAG,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,MAAM,MAAM,UAAU,CAAC,CAAC;AAAA,EAC1E;AAEA,WAAS,SAAS,OAAO,QAAQ;AAC/B,QAAI,MAAM,QAAQ,OAAO;AACvB,aAAO;AAAA,IACT;AAEA,QAAI,cAAc,KAAK,KAAK,mBAAmB,MAAM,KAAK,CAAC,gBAAgB,MAAM,GAAG;AAElF,4BAAsB,KAAK;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,iBAAiB,KAAK,KAAK,gBAAgB,MAAM,KAAK,CAAC,mBAAmB,MAAM,GAAG;AAErF,4BAAsB,KAAK;AAC3B,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/sync-language.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import { fieldToString } from "./utils.js";
|
|
3
2
|
export default function() {
|
|
4
3
|
return {
|
|
@@ -46,7 +45,7 @@ export default function() {
|
|
|
46
45
|
return handleZxx(remainingFields, [...result, message2]);
|
|
47
46
|
}
|
|
48
47
|
if (validateMode) {
|
|
49
|
-
const clonedField =
|
|
48
|
+
const clonedField = structuredClone(field);
|
|
50
49
|
clonedField.subfields = otherSubfields;
|
|
51
50
|
const modifiedString2 = fieldToString(clonedField);
|
|
52
51
|
const message2 = `Modify '${originalString}' => '${modifiedString2}`;
|
|
@@ -80,7 +79,7 @@ export default function() {
|
|
|
80
79
|
if (!firstRelevantSubfield || !transferableValue(firstRelevantSubfield.value) || f041.subfields.some((sf) => sf.code === "2") || lang008 === firstRelevantSubfield.value) {
|
|
81
80
|
return [];
|
|
82
81
|
}
|
|
83
|
-
const cloned008 =
|
|
82
|
+
const cloned008 = structuredClone(f008);
|
|
84
83
|
cloned008.value = `${f008.value.substring(0, 35)}${firstRelevantSubfield.value}${f008.value.substring(38)}`;
|
|
85
84
|
if (!validateMode) {
|
|
86
85
|
f008.value = cloned008.value;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/sync-language.js"],
|
|
4
|
-
"sourcesContent": ["// Author(s): Nicholas Volk\n\n//import createDebugLogger from 'debug';\
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["// Author(s): Nicholas Volk\n\n//import createDebugLogger from 'debug';\n\nimport {fieldToString} from './utils.js';\n\n// const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sync-language');\n\nexport default function () {\n\n return {\n description: 'Sync 008/35-37 and 041$a/$d',\n validate, fix\n };\n\n function getSubfieldCodeFor041(record) {\n const c = record?.leader[6];\n if (c === 'i' || c === 'j') {\n return 'd';\n }\n return 'a';\n }\n\n function transferableValue(val) {\n if (val.match(/^[a-z]{3}$/u)) {\n // 'zxx' is an exception to the otherwise relatively transitive 008/35-37 <=> 041$a relationship...\n // https://wiki.helsinki.fi/xwiki/bin/view/rdasovellusohje/RDA-kuvailu%20MARC%2021%20-formaatilla/RDA-sovellusohje/008%20Kontrollikentt%C3%A4/#Hmp.35-37Kieli\n // \"008-kielikoodi on sama kuin ensimm\u00E4inen 041-kent\u00E4n a- (tai d-) osakentt\u00E4. Poikkeuksen muodostaa koodi zxx:\n // jos tietueen 008ssa on kielikoodi zxx (ei kielellist\u00E4 sis\u00E4lt\u00F6\u00E4, esim. soitinmusiikki), tietueessa ei voi olla 041 a- eik\u00E4 d-osakentt\u00E4\u00E4.\"\n // So 'zxx' ain't transferable (done here), and it should be removed from 041 ($a and $d at least, not done here nor elsewhere at the moment...)\n // Note that 'mul' and 'und' are treated (in this validator) the same as normal values.\n if (val === 'zxx') {\n return false;\n }\n return true;\n }\n return false;\n }\n\n\n function zxxRemoval041(record, field, validateMode) {\n const fields041 = record.fields.filter(f => f.tag === '041');\n\n return handleZxx(fields041);\n\n function handleZxx(fields, result = []) {\n const [currField, ...remainingFields] = fields;\n if ( !currField) {\n return result;\n }\n // Theoretically 'zxx' might mean something in ISO-639-3 or some other language code list:\n if (!field.subfields || field.subfields.some(sf => sf.code === '2')) {\n return handleZxx(remainingFields, result);\n }\n // Presumable 'zxx' is bad in any data subfield, not just 'a' and 'd':\n const otherSubfields = field.subfields.some(sf => !sf.code.match(/^[a-z]$/u || sf.value !== 'zxx'));\n if (otherSubfields.length === field.subfields.length) {\n return handleZxx(remainingFields, result);\n }\n const originalString = fieldToString(field);\n if (otherSubfields.length === 0) {\n const message = `Remove '${originalString}'`;\n if (!validateMode) {\n record.removeField(field);\n }\n return handleZxx(remainingFields, [...result, message]);\n }\n // Some subfields are removed:\n if (validateMode) {\n // NB! In validation field is not really deleted, and the non-deleted field might trigger other fixes (than won't be done to a deleted field) later on\n const clonedField = structuredClone(field);\n clonedField.subfields = otherSubfields;\n const modifiedString = fieldToString(clonedField);\n const message = `Modify '${originalString}' => '${modifiedString}`;\n return handleZxx(remainingFields, [...result, message]);\n }\n field.subfields = otherSubfields;\n const modifiedString = fieldToString(field);\n const message = `Modify '${originalString}' => '${modifiedString}`;\n return handleZxx(remainingFields, [...result, message]);\n }\n\n\n }\n\n function sync008And041(record, validateMode) {\n const f008 = record.fields.find(f => f.tag === '008');\n const f041 = record.fields.find(f => f.tag === '041');\n\n if (!f008 || f008.value.length !== 40) { // Some sanity checks\n return [];\n }\n\n const subfieldCode = getSubfieldCodeFor041(record);\n const lang008 = f008.value.substring(35, 38);\n\n if (!f041) {\n // Insert missing 041\n if (transferableValue(lang008)) {\n const newField = {'tag': '041', 'ind1': ' ', 'ind2': ' ', 'subfields': [ {'code': subfieldCode, 'value': lang008}]};\n if (!validateMode) {\n record.insertField(newField);\n }\n return [ `Add '${fieldToString(newField)}'` ];\n }\n // Can't do anything, and we only report this we can fix...\n return [];\n }\n\n const firstRelevantSubfield = f041.subfields.find(sf => sf.code === subfieldCode);\n\n // NB! If $2 is set, *never* copy it's value to 008/35-37... Otherwise we might a loop in Aleph.\n // Note that if $2 is used, 008/35-37 should actually be '|||'. Now we just aggressively leave it alone.\n if (!firstRelevantSubfield || !transferableValue(firstRelevantSubfield.value) || f041.subfields.some(sf => sf.code === '2') || lang008 === firstRelevantSubfield.value) {\n return []\n }\n\n // Update 008/35-37:\n const cloned008 = structuredClone(f008);\n cloned008.value = `${f008.value.substring(0, 35)}${firstRelevantSubfield.value}${f008.value.substring(38)}`;\n\n\n if (!validateMode) {\n f008.value = cloned008.value;\n }\n return [ `Modify '${f008.value}' to '${cloned008.value}'` ];\n }\n\n function fix(record, validateMode = false) {\n const zxxMessages = zxxRemoval041(record, validateMode);\n\n const syncMessages = sync008And041(record, validateMode);\n\n const messages = [...zxxMessages, ...syncMessages];\n\n if (validateMode) {\n return {message: messages, valid: messages.length === 0};\n }\n return {message: [], fix: messages, valid: true};\n }\n\n\n function validate(record) {\n return fix(record, true);\n }\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["message", "modifiedString"]
|
|
7
7
|
}
|
package/dist/translate-terms.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import createDebugLogger from "debug";
|
|
3
2
|
import { fieldHasSubfield, fieldToString, nvdebug } from "./utils.js";
|
|
4
3
|
const debug = createDebugLogger("@natlibfi/marc-record-validators-melinda:translate-terms");
|
|
@@ -70,7 +69,7 @@ export default function() {
|
|
|
70
69
|
const twoLetterOtherLang = swapLanguageCodeBetweenLanguages(changeAbbr(lexAndLang.lang));
|
|
71
70
|
const prefLabel = prefLabels.find((l) => l.lang === twoLetterOtherLang);
|
|
72
71
|
const sfA = { "code": "a", "value": prefLabel.value };
|
|
73
|
-
const sf0 =
|
|
72
|
+
const sf0 = structuredClone(field.subfields.find((sf) => sf.code === "0"));
|
|
74
73
|
const sf2 = { "code": "2", "value": `${lexAndLang.lex}/${lexAndLang.lang === "fin" ? "swe" : "fin"}` };
|
|
75
74
|
const newField = { tag: field.tag, ind1: field.ind1, ind2: field.ind2, subfields: [sfA, sf2, sf0] };
|
|
76
75
|
return newField;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/translate-terms.js"],
|
|
4
|
-
"sourcesContent": ["import clone from 'clone';\nimport createDebugLogger from 'debug';\nimport {fieldHasSubfield, fieldToString, nvdebug} from './utils.js';\n\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:translate-terms');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst defaultTags = ['648', '650', '651', '655'];\n\nconst swapLanguageCode = {'fin': 'swe', 'fi': 'sv', 'sv': 'fi', 'swe': 'fin'};\nconst changeAbbrHash = {'fi': 'fin', 'fin': 'fi', 'sv': 'swe', 'swe': 'sv'};\n\nconst termCache = {};\n\n// Author(s): Nicholas Volk\n// eslint-disable-next-line max-lines-per-function\nexport default function () {\n\n\n return {\n description: 'Translate yso (648, 650, 651) and slm (655) terms (FIN <=> SWE)',\n validate, fix\n };\n\n async function fix(record) {\n const newFields = await getFields(record, defaultTags, []);\n\n newFields.forEach(nf => nvdebug(`Add new field '${fieldToString(nf)}'`, debugDev));\n\n newFields.forEach(nf => record.insertField(nf));\n\n const newFieldsAsStrings = newFields.map(f => fieldToString(f));\n\n\n return {message: [], fix: newFieldsAsStrings, valid: true};\n }\n\n async function validate(record) {\n const newFields = await getFields(record, defaultTags, []);\n if (newFields.length === 0) {\n return {'message': [], 'valid': true};\n }\n const messages = newFields.map(f => fieldToString(f));\n\n return {'message': messages, 'valid': false};\n }\n\n async function getFields(record, tags, fieldsToAdd) {\n const [currTag, ...remainingTags] = tags;\n if (!currTag) {\n return fieldsToAdd;\n }\n const missingFields = await deriveMissingFields(record, currTag);\n\n const tmp = await getFields(record, remainingTags, [...fieldsToAdd, ...missingFields]);\n return tmp;\n }\n\n function getPairlessFinnishAndSwedishFields(record, tag) {\n const expectedLex = mapTagToLex(tag);\n if (!expectedLex) {\n return [];\n }\n const fields = record.get(tag);\n const finnishFields = fields.filter(f => isTranslatable(f, 'fin'));\n const swedishFields = fields.filter(f => isTranslatable(f, 'swe'));\n const finnishOnly = getMisses(finnishFields, swedishFields);\n const swedishOnly = getMisses(swedishFields, finnishFields);\n\n //nvdebug(` Looking at ${finnishOnly.length} + ${swedishOnly.length} fields`, debugDev);\n return [...finnishOnly, ...swedishOnly].filter(f => tagAndFieldAgree(f));\n\n function tagAndFieldAgree(field) {\n // Check that tag and $2 value are pairable:\n const lexData = getLexiconAndLanguage(field); // $2 data\n return expectedLex === lexData.lex;\n }\n }\n\n async function deriveMissingFields(record, tag) {\n const pairlessFields = getPairlessFinnishAndSwedishFields(record, tag);\n\n // Dunno how to handle loop+promise combo in our normal coding style. Spent half a day trying... (I reckon it takes like 2 minuts to do this properly...)\n // Did a proper implementation in drop-terms.js...\n let prefLabels = [];\n for (let i=0; i < pairlessFields.length; i += 1) {\n prefLabels[i] = await getPrefLabel(pairlessFields[i]);\n }\n\n const missingFields = pairlessFields.map((f, i) => pairField(f, prefLabels[i]));\n return missingFields.filter(f => f);\n }\n\n function pairField(field, prefLabels) {\n if (!prefLabels) {\n return undefined;\n }\n //nvdebug(`pairField() WP 1: ${fieldToString(field)}`, debugDev);\n const lexAndLang = getLexiconAndLanguage(field);\n //nvdebug(`pairField() WP 2: ${JSON.stringify(lexAndLang)}`, debugDev);\n const twoLetterOtherLang = swapLanguageCodeBetweenLanguages(changeAbbr(lexAndLang.lang));\n const prefLabel = prefLabels.find(l => l.lang === twoLetterOtherLang);\n //nvdebug(`pairField() WP 4: ${JSON.stringify(prefLabel)}`, debugDev);\n const sfA = {'code': 'a', 'value': prefLabel.value}; // field.subfields.field(sf => sf.code === 'a');\n const sf0 = clone(field.subfields.find(sf => sf.code === '0'));\n const sf2 = {'code': '2', 'value': `${lexAndLang.lex}/${lexAndLang.lang === 'fin' ? 'swe' : 'fin'}`}; // swap fin <=> swe\n const newField = {tag: field.tag, ind1: field.ind1, ind2: field.ind2, subfields: [sfA, sf2, sf0]};\n return newField;\n }\n\n\n\n async function getPrefLabel(field) {\n // Pre-requisite: tag vs $2 correlation has already been checked!\n const uri = fieldToUri(field);\n if (!uri) { // $0 is invalid or sumthing\n return undefined;\n }\n\n const data = await getTermData(uri);\n\n if (!data) { // Sanity check. Miss caused by illegal id etc.\n nvdebug(`No labels found for ${uri}`, debugDev);\n return undefined;\n }\n\n const prefLabels = data.prefLabel;\n\n const lexData = getLexiconAndLanguage(field); // $2 data\n const lang = changeAbbr(lexData.lang); // fi <=> fin (finto use 2 chars, we use 3-letters)\n const subfieldA = field.subfields.find(sf => sf.code === 'a');\n\n if (isLabel(prefLabels, subfieldA.value, lang)) {\n return prefLabels;\n }\n return undefined;\n }\n\n\n function fieldToUri(field) {\n const lex = mapTagToLex(field.tag);\n\n const subfield0 = field.subfields.find(sf => sf.code === '0');\n const id = subfield0.value.replace(/^[^0-9]+/u, '');\n return buildUri(lex, id);\n }\n\n function isTranslatable(field, lang) {\n const fieldAsString = fieldToString(field);\n\n // We should probably allow an optional $8 as the first subfield.\n if (!fieldAsString.match(/^... #7 (?: \u20218 [^\u2021]+ )*\u2021a [^\u2021]+ \u20212 [^\u2021]+ \u20210 [^\u2021]+(?: \u20219 [A-Z]+<(?:KEEP|DROP)>)*$/u)) {\n return false;\n }\n const lex = mapTagToLex(field.tag);\n const lexLang = `${lex}/${lang}`;\n if (!fieldHasSubfield(field, '2', lexLang)) {\n return false;\n }\n return fieldHasValidSubfield0(field);\n }\n\n\n function getMisses(fieldList1, fieldList2) {\n return fieldList1.filter(f => !hasSubfield0Match(f, fieldList2));\n }\n\n function hasSubfield0Match(field, pairFields) {\n const subfield0 = field.subfields.find(sf => sf.code === '0');\n return pairFields.some(f => f.subfields.some(sf => sf.code === '0' && sf.value === subfield0.value));\n }\n\n\n\n\n\n /*\n function getValidIdentifiers(record, tag, lang) {\n const lex = mapTagToLex(tag);\n const subfield2Value = `${lex}/${lang}`;\n const candFields = record.get(tag).filter(f => f.subfields.some(sf => sf.code === '2' && sf.value === subfield2Value)); // TODO: filter\n return [];\n }\n */\n\n}\n\nexport function fieldHasValidSubfield0(field, defaultLex = undefined) {\n const lex = defaultLex || mapTagToLex(field.tag);\n return field.subfields.some(sf => isValidSubfield0(sf, lex));\n}\n\nexport function isValidSubfield0(subfield, lex = '???') {\n if (subfield.code !== '0') {\n return false;\n }\n // 2025-12-10: supports both http and https as well. Note that translation will copy the original $0 idenfifier even though it might be non-standard.\n // Note that currently 'http' is teh standard!!!\n if (['yso', 'yso/fin', 'yso/swe'].includes(lex) && subfield.value.match(/^https?:\\/\\/www\\.yso\\.fi\\/onto\\/yso\\/p[0-9]+$/u)) {\n return true;\n }\n if (['slm', 'slm/fin', 'slm/swe'].includes(lex) && subfield.value.match(/^https?:\\/\\/urn\\.fi\\/URN:NBN:fi:au:slm:s[0-9]+$/u)) {\n return true;\n }\n return false;\n}\n\n\nexport function buildUri(lex, id) {\n if (['yso', 'yso/fin', 'yso/swe'].includes(lex)) {\n //return `http%3A%2F%2Fwww.yso.fi%2Fonto%2Fyso%2Fp${id}`;\n return `http://www.yso.fi/onto/yso/p${id}`;\n }\n if (['slm', 'slm/fin', 'slm/swe'].includes(lex)) {\n return `http://urn.fi/URN:NBN:fi:au:slm:s${id}`;\n }\n return undefined;\n}\n\nfunction mapTagToLex(tag) {\n if (tag === '655') {\n return 'slm';\n }\n return 'yso';\n}\n\nexport async function getTermData(uri) {\n nvdebug(`getTermData(${uri})`, debugDev);\n if (termCache[uri]) { // Don't think current implementation uses the cache any more.\n //nvdebug(`CACHED ${uri}`, debugDev);\n return termCache[uri];\n }\n const tmp = await getTermDataFromFinto(uri);\n termCache[uri] = tmp;\n return tmp;\n}\n\nasync function getTermDataFromFinto(uri) {\n const headers = {'Accept': 'application/json'};\n const uri2 = swaggerQuery(uri);\n\n const response = await fetch(uri2, {method: 'GET', headers});\n if (!response.ok) {\n return undefined;\n }\n const json = await response.json();\n\n if (!json.graph) {\n return undefined;\n }\n const arr = json.graph;\n const [hit] = arr.filter(row => row.uri === uri);\n const subset = {\n prefLabel: processLabel(hit?.prefLabel || undefined),\n altLabel: processLabel(hit?.altLabel || undefined)\n };\n //nvdebug(`NEW JSON: ${JSON.stringify(hit)}`, debugDev);\n\n return subset;\n\n function swaggerQuery(uri) {\n // This would work for only yso, not yso-paikat etc `https://api.finto.fi/rest/v1/yso/data?format=application%2Fjson&uri=${uri}`;\n return `https://api.finto.fi/rest/v1/data?uri=${uri}&format=application%2Fjson`; // This is simpler, but contains more irrelevant data\n }\n\n function processLabel(label) {\n if (typeof label === 'object') {\n if (Array.isArray(label)) {\n return label;\n }\n return [label];\n }\n return [];\n }\n}\n\nexport function getLexiconAndLanguage(field) {\n // Assumes that field has exactly one $2\n const subfield2 = field.subfields.find(sf => sf.code === '2');\n if (subfield2) {\n if (subfield2.value === 'slm/fin') {\n return {'lex': 'slm', 'lang': 'fin'};\n }\n if (subfield2.value === 'slm/swe') {\n return {'lex': 'slm', 'lang': 'swe'};\n }\n if (subfield2.value === 'yso/fin') {\n return {'lex': 'yso', 'lang': 'fin'};\n }\n if (subfield2.value === 'yso/swe') {\n return {'lex': 'yso', 'lang': 'swe'};\n }\n }\n return {};\n}\n\n\n\nexport function isLabel(labels, term, lang = undefined) {\n const twoLetterLanguageCode = lang && lang.length === 3 ? changeAbbr(lang) : lang;\n return labels.some(l => isMatch(l));\n\n function isMatch(label) {\n if (label.value !== term) {\n return false;\n }\n if (!twoLetterLanguageCode) { // If language code is not defined, any hit will do\n return true;\n }\n return label.lang === twoLetterLanguageCode;\n }\n}\n\n\n function changeAbbr(abbr) {\n if (changeAbbrHash[abbr]) {\n return changeAbbrHash[abbr];\n }\n return abbr;\n }\n\n\n function swapLanguageCodeBetweenLanguages(code) {\n if (swapLanguageCode[code]) {\n return swapLanguageCode[code];\n }\n return code;\n }"],
|
|
5
|
-
"mappings": "AAAA,OAAO,
|
|
4
|
+
"sourcesContent": ["import createDebugLogger from 'debug';\nimport {fieldHasSubfield, fieldToString, nvdebug} from './utils.js';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:translate-terms');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst defaultTags = ['648', '650', '651', '655'];\n\nconst swapLanguageCode = {'fin': 'swe', 'fi': 'sv', 'sv': 'fi', 'swe': 'fin'};\nconst changeAbbrHash = {'fi': 'fin', 'fin': 'fi', 'sv': 'swe', 'swe': 'sv'};\n\nconst termCache = {};\n\n// Author(s): Nicholas Volk\n// eslint-disable-next-line max-lines-per-function\nexport default function () {\n\n\n return {\n description: 'Translate yso (648, 650, 651) and slm (655) terms (FIN <=> SWE)',\n validate, fix\n };\n\n async function fix(record) {\n const newFields = await getFields(record, defaultTags, []);\n\n newFields.forEach(nf => nvdebug(`Add new field '${fieldToString(nf)}'`, debugDev));\n\n newFields.forEach(nf => record.insertField(nf));\n\n const newFieldsAsStrings = newFields.map(f => fieldToString(f));\n\n\n return {message: [], fix: newFieldsAsStrings, valid: true};\n }\n\n async function validate(record) {\n const newFields = await getFields(record, defaultTags, []);\n if (newFields.length === 0) {\n return {'message': [], 'valid': true};\n }\n const messages = newFields.map(f => fieldToString(f));\n\n return {'message': messages, 'valid': false};\n }\n\n async function getFields(record, tags, fieldsToAdd) {\n const [currTag, ...remainingTags] = tags;\n if (!currTag) {\n return fieldsToAdd;\n }\n const missingFields = await deriveMissingFields(record, currTag);\n\n const tmp = await getFields(record, remainingTags, [...fieldsToAdd, ...missingFields]);\n return tmp;\n }\n\n function getPairlessFinnishAndSwedishFields(record, tag) {\n const expectedLex = mapTagToLex(tag);\n if (!expectedLex) {\n return [];\n }\n const fields = record.get(tag);\n const finnishFields = fields.filter(f => isTranslatable(f, 'fin'));\n const swedishFields = fields.filter(f => isTranslatable(f, 'swe'));\n const finnishOnly = getMisses(finnishFields, swedishFields);\n const swedishOnly = getMisses(swedishFields, finnishFields);\n\n //nvdebug(` Looking at ${finnishOnly.length} + ${swedishOnly.length} fields`, debugDev);\n return [...finnishOnly, ...swedishOnly].filter(f => tagAndFieldAgree(f));\n\n function tagAndFieldAgree(field) {\n // Check that tag and $2 value are pairable:\n const lexData = getLexiconAndLanguage(field); // $2 data\n return expectedLex === lexData.lex;\n }\n }\n\n async function deriveMissingFields(record, tag) {\n const pairlessFields = getPairlessFinnishAndSwedishFields(record, tag);\n\n // Dunno how to handle loop+promise combo in our normal coding style. Spent half a day trying... (I reckon it takes like 2 minuts to do this properly...)\n // Did a proper implementation in drop-terms.js...\n let prefLabels = [];\n for (let i=0; i < pairlessFields.length; i += 1) {\n prefLabels[i] = await getPrefLabel(pairlessFields[i]);\n }\n\n const missingFields = pairlessFields.map((f, i) => pairField(f, prefLabels[i]));\n return missingFields.filter(f => f);\n }\n\n function pairField(field, prefLabels) {\n if (!prefLabels) {\n return undefined;\n }\n //nvdebug(`pairField() WP 1: ${fieldToString(field)}`, debugDev);\n const lexAndLang = getLexiconAndLanguage(field);\n //nvdebug(`pairField() WP 2: ${JSON.stringify(lexAndLang)}`, debugDev);\n const twoLetterOtherLang = swapLanguageCodeBetweenLanguages(changeAbbr(lexAndLang.lang));\n const prefLabel = prefLabels.find(l => l.lang === twoLetterOtherLang);\n //nvdebug(`pairField() WP 4: ${JSON.stringify(prefLabel)}`, debugDev);\n const sfA = {'code': 'a', 'value': prefLabel.value}; // field.subfields.field(sf => sf.code === 'a');\n const sf0 = structuredClone(field.subfields.find(sf => sf.code === '0'));\n const sf2 = {'code': '2', 'value': `${lexAndLang.lex}/${lexAndLang.lang === 'fin' ? 'swe' : 'fin'}`}; // swap fin <=> swe\n const newField = {tag: field.tag, ind1: field.ind1, ind2: field.ind2, subfields: [sfA, sf2, sf0]};\n return newField;\n }\n\n\n\n async function getPrefLabel(field) {\n // Pre-requisite: tag vs $2 correlation has already been checked!\n const uri = fieldToUri(field);\n if (!uri) { // $0 is invalid or sumthing\n return undefined;\n }\n\n const data = await getTermData(uri);\n\n if (!data) { // Sanity check. Miss caused by illegal id etc.\n nvdebug(`No labels found for ${uri}`, debugDev);\n return undefined;\n }\n\n const prefLabels = data.prefLabel;\n\n const lexData = getLexiconAndLanguage(field); // $2 data\n const lang = changeAbbr(lexData.lang); // fi <=> fin (finto use 2 chars, we use 3-letters)\n const subfieldA = field.subfields.find(sf => sf.code === 'a');\n\n if (isLabel(prefLabels, subfieldA.value, lang)) {\n return prefLabels;\n }\n return undefined;\n }\n\n\n function fieldToUri(field) {\n const lex = mapTagToLex(field.tag);\n\n const subfield0 = field.subfields.find(sf => sf.code === '0');\n const id = subfield0.value.replace(/^[^0-9]+/u, '');\n return buildUri(lex, id);\n }\n\n function isTranslatable(field, lang) {\n const fieldAsString = fieldToString(field);\n\n // We should probably allow an optional $8 as the first subfield.\n if (!fieldAsString.match(/^... #7 (?: \u20218 [^\u2021]+ )*\u2021a [^\u2021]+ \u20212 [^\u2021]+ \u20210 [^\u2021]+(?: \u20219 [A-Z]+<(?:KEEP|DROP)>)*$/u)) {\n return false;\n }\n const lex = mapTagToLex(field.tag);\n const lexLang = `${lex}/${lang}`;\n if (!fieldHasSubfield(field, '2', lexLang)) {\n return false;\n }\n return fieldHasValidSubfield0(field);\n }\n\n\n function getMisses(fieldList1, fieldList2) {\n return fieldList1.filter(f => !hasSubfield0Match(f, fieldList2));\n }\n\n function hasSubfield0Match(field, pairFields) {\n const subfield0 = field.subfields.find(sf => sf.code === '0');\n return pairFields.some(f => f.subfields.some(sf => sf.code === '0' && sf.value === subfield0.value));\n }\n\n\n\n\n\n /*\n function getValidIdentifiers(record, tag, lang) {\n const lex = mapTagToLex(tag);\n const subfield2Value = `${lex}/${lang}`;\n const candFields = record.get(tag).filter(f => f.subfields.some(sf => sf.code === '2' && sf.value === subfield2Value)); // TODO: filter\n return [];\n }\n */\n\n}\n\nexport function fieldHasValidSubfield0(field, defaultLex = undefined) {\n const lex = defaultLex || mapTagToLex(field.tag);\n return field.subfields.some(sf => isValidSubfield0(sf, lex));\n}\n\nexport function isValidSubfield0(subfield, lex = '???') {\n if (subfield.code !== '0') {\n return false;\n }\n // 2025-12-10: supports both http and https as well. Note that translation will copy the original $0 idenfifier even though it might be non-standard.\n // Note that currently 'http' is teh standard!!!\n if (['yso', 'yso/fin', 'yso/swe'].includes(lex) && subfield.value.match(/^https?:\\/\\/www\\.yso\\.fi\\/onto\\/yso\\/p[0-9]+$/u)) {\n return true;\n }\n if (['slm', 'slm/fin', 'slm/swe'].includes(lex) && subfield.value.match(/^https?:\\/\\/urn\\.fi\\/URN:NBN:fi:au:slm:s[0-9]+$/u)) {\n return true;\n }\n return false;\n}\n\n\nexport function buildUri(lex, id) {\n if (['yso', 'yso/fin', 'yso/swe'].includes(lex)) {\n //return `http%3A%2F%2Fwww.yso.fi%2Fonto%2Fyso%2Fp${id}`;\n return `http://www.yso.fi/onto/yso/p${id}`;\n }\n if (['slm', 'slm/fin', 'slm/swe'].includes(lex)) {\n return `http://urn.fi/URN:NBN:fi:au:slm:s${id}`;\n }\n return undefined;\n}\n\nfunction mapTagToLex(tag) {\n if (tag === '655') {\n return 'slm';\n }\n return 'yso';\n}\n\nexport async function getTermData(uri) {\n nvdebug(`getTermData(${uri})`, debugDev);\n if (termCache[uri]) { // Don't think current implementation uses the cache any more.\n //nvdebug(`CACHED ${uri}`, debugDev);\n return termCache[uri];\n }\n const tmp = await getTermDataFromFinto(uri);\n termCache[uri] = tmp;\n return tmp;\n}\n\nasync function getTermDataFromFinto(uri) {\n const headers = {'Accept': 'application/json'};\n const uri2 = swaggerQuery(uri);\n\n const response = await fetch(uri2, {method: 'GET', headers});\n if (!response.ok) {\n return undefined;\n }\n const json = await response.json();\n\n if (!json.graph) {\n return undefined;\n }\n const arr = json.graph;\n const [hit] = arr.filter(row => row.uri === uri);\n const subset = {\n prefLabel: processLabel(hit?.prefLabel || undefined),\n altLabel: processLabel(hit?.altLabel || undefined)\n };\n //nvdebug(`NEW JSON: ${JSON.stringify(hit)}`, debugDev);\n\n return subset;\n\n function swaggerQuery(uri) {\n // This would work for only yso, not yso-paikat etc `https://api.finto.fi/rest/v1/yso/data?format=application%2Fjson&uri=${uri}`;\n return `https://api.finto.fi/rest/v1/data?uri=${uri}&format=application%2Fjson`; // This is simpler, but contains more irrelevant data\n }\n\n function processLabel(label) {\n if (typeof label === 'object') {\n if (Array.isArray(label)) {\n return label;\n }\n return [label];\n }\n return [];\n }\n}\n\nexport function getLexiconAndLanguage(field) {\n // Assumes that field has exactly one $2\n const subfield2 = field.subfields.find(sf => sf.code === '2');\n if (subfield2) {\n if (subfield2.value === 'slm/fin') {\n return {'lex': 'slm', 'lang': 'fin'};\n }\n if (subfield2.value === 'slm/swe') {\n return {'lex': 'slm', 'lang': 'swe'};\n }\n if (subfield2.value === 'yso/fin') {\n return {'lex': 'yso', 'lang': 'fin'};\n }\n if (subfield2.value === 'yso/swe') {\n return {'lex': 'yso', 'lang': 'swe'};\n }\n }\n return {};\n}\n\n\n\nexport function isLabel(labels, term, lang = undefined) {\n const twoLetterLanguageCode = lang && lang.length === 3 ? changeAbbr(lang) : lang;\n return labels.some(l => isMatch(l));\n\n function isMatch(label) {\n if (label.value !== term) {\n return false;\n }\n if (!twoLetterLanguageCode) { // If language code is not defined, any hit will do\n return true;\n }\n return label.lang === twoLetterLanguageCode;\n }\n}\n\n\n function changeAbbr(abbr) {\n if (changeAbbrHash[abbr]) {\n return changeAbbrHash[abbr];\n }\n return abbr;\n }\n\n\n function swapLanguageCodeBetweenLanguages(code) {\n if (swapLanguageCode[code]) {\n return swapLanguageCode[code];\n }\n return code;\n }"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["uri"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/NatLibFi/marc-record-validators-melinda"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
|
-
"version": "13.0.
|
|
17
|
+
"version": "13.0.3",
|
|
18
18
|
"main": "./dist/index.js",
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@natlibfi/iso9-1995": "^1.1.4",
|
|
39
39
|
"@natlibfi/issn-verify": "^2.0.3",
|
|
40
|
-
"@natlibfi/marc-record": "^
|
|
40
|
+
"@natlibfi/marc-record": "^11.0.3",
|
|
41
41
|
"@natlibfi/marc-record-serializers": "^11.0.3",
|
|
42
42
|
"@natlibfi/marc-record-validate": "^9.0.1",
|
|
43
43
|
"@natlibfi/melinda-commons": "^15.0.2",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
import createDebugLogger from 'debug';
|
|
12
12
|
import {MarcRecord} from '@natlibfi/marc-record';
|
|
13
13
|
import {default as fix33X} from './fix-33X.js';
|
|
@@ -250,7 +250,7 @@ function field410To490And810(field, record) { // might be generic... if so, move
|
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
const field810 =
|
|
253
|
+
const field810 = structuredClone(field);
|
|
254
254
|
|
|
255
255
|
field.tag = '490';
|
|
256
256
|
field.ind1 = '1';
|
|
@@ -270,7 +270,7 @@ function field440To490And830(field, record) { // might be generic... if so, move
|
|
|
270
270
|
return;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
const field830 =
|
|
273
|
+
const field830 = structuredClone(field);
|
|
274
274
|
|
|
275
275
|
field.tag = '490';
|
|
276
276
|
field.ind1 = '1';
|
package/src/cyrillux.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//import createDebugLogger from 'debug';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import XRegExp from 'xregexp';
|
|
4
4
|
import * as iso9 from '@natlibfi/iso9-1995';
|
|
5
5
|
import * as sfs4900 from '@natlibfi/sfs-4900';
|
|
@@ -96,7 +96,7 @@ export default function (config = {}) {
|
|
|
96
96
|
function validateField(field, res, record) {
|
|
97
97
|
const orig = fieldToString(field);
|
|
98
98
|
|
|
99
|
-
const normalizedFields = processField(
|
|
99
|
+
const normalizedFields = processField(structuredClone(field), record);
|
|
100
100
|
const mod = fieldsToString(normalizedFields).replace(/\t__SEPARATOR__\t/ug, ', ').replace(/ (‡6 [0-9][0-9][0-9])-[0-9][0-9]+/gu, ' $1-NN');
|
|
101
101
|
if (orig !== mod) { // Fail as the input is "broken"/"crap"/sumthing
|
|
102
102
|
res.message.push(`CHANGE: ${orig} => ${mod}`);
|
|
@@ -249,7 +249,7 @@ export default function (config = {}) {
|
|
|
249
249
|
return initialSubfield;
|
|
250
250
|
}
|
|
251
251
|
// Try to use existing subfield
|
|
252
|
-
const [subfield6] = subfields.filter(sf => sf.code === '6').map(sf =>
|
|
252
|
+
const [subfield6] = subfields.filter(sf => sf.code === '6').map(sf => structuredClone(sf));
|
|
253
253
|
if (subfield6) {
|
|
254
254
|
resetSubfield6Tag(subfield6, tag); // Should we update occurrence number?
|
|
255
255
|
return subfield6;
|
|
@@ -267,7 +267,7 @@ export default function (config = {}) {
|
|
|
267
267
|
const newSubfield9 = fieldHasSubfield(field, '9', cyrillicTrans) ? [] : [{code: '9', value: cyrillicTrans}];
|
|
268
268
|
const subfields = [
|
|
269
269
|
newSubfield6,
|
|
270
|
-
...field.subfields.filter(sf => sf.code !== '6').map(sf =>
|
|
270
|
+
...field.subfields.filter(sf => sf.code !== '6').map(sf => structuredClone(sf)),
|
|
271
271
|
...newSubfield9
|
|
272
272
|
];
|
|
273
273
|
|
|
@@ -343,7 +343,7 @@ export default function (config = {}) {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
function createFieldForSfs4900Comparison(field, tag) {
|
|
346
|
-
const clonedField =
|
|
346
|
+
const clonedField = structuredClone(field);
|
|
347
347
|
clonedField.tag = tag;
|
|
348
348
|
clonedField.subfields = clonedField.subfields.filter(sf => sf.code !== '9' || sf.value !== sfs4900Trans);
|
|
349
349
|
return fieldStripPunctuation(clonedField);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import createDebugLogger from 'debug';
|
|
2
2
|
import {fieldToString, nvdebug, subfieldToString} from './utils.js';
|
|
3
3
|
import {MARCXML} from '@natlibfi/marc-record-serializers';
|
|
4
|
-
|
|
5
|
-
import clone from 'clone';
|
|
4
|
+
|
|
6
5
|
import {default as createNatlibfiSruClient} from '@natlibfi/sru-client';
|
|
7
6
|
import {fieldFixPunctuation} from './punctuation2.js';
|
|
8
7
|
|
|
@@ -91,7 +90,7 @@ export default function () {
|
|
|
91
90
|
return fix490x(recordType, remainingFields, reallyFix, message);
|
|
92
91
|
}
|
|
93
92
|
// validators:
|
|
94
|
-
const clonedField =
|
|
93
|
+
const clonedField = structuredClone(currField);
|
|
95
94
|
const originalString = fieldToString(clonedField);
|
|
96
95
|
clonedField.subfields = clonedField.subfields.filter(sf => !deletableStrings.includes(subfieldToString(sf)));
|
|
97
96
|
|
package/src/drop-terms.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Author(s): Nicholas Volk
|
|
2
2
|
|
|
3
|
-
import clone from 'clone';
|
|
4
3
|
import createDebugLogger from 'debug';
|
|
4
|
+
|
|
5
5
|
import {fieldToString, nvdebug} from './utils.js';
|
|
6
6
|
import {getLexiconAndLanguage, getTermData, isLabel, isValidSubfield0} from './translate-terms.js';
|
|
7
7
|
|
|
@@ -35,7 +35,7 @@ export default function (config = defaultConfig) {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
async function fix(record, validateMode = false) {
|
|
38
|
-
const clonedFields = record.fields.map(f =>
|
|
38
|
+
const clonedFields = record.fields.map(f => structuredClone(f));
|
|
39
39
|
|
|
40
40
|
const results = await processFields(clonedFields);
|
|
41
41
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//import createDebugLogger from 'debug';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import {fieldToString} from './utils.js';
|
|
4
4
|
// Author(s): Nicholas Volk
|
|
5
5
|
// NB! CR 008/24 vs 008/25-27 is not supported yet!
|
|
@@ -40,7 +40,7 @@ export default function () {
|
|
|
40
40
|
}
|
|
41
41
|
const orig = fieldToString(field);
|
|
42
42
|
|
|
43
|
-
const normalizedField = justifyAndSortField008CharacterGroups(
|
|
43
|
+
const normalizedField = justifyAndSortField008CharacterGroups(structuredClone(field), typeOfMaterial);
|
|
44
44
|
const mod = fieldToString(normalizedField);
|
|
45
45
|
if (orig !== mod) { // Fail as the input is "broken"/"crap"/sumthing
|
|
46
46
|
res.message.push(`TODO: '${orig}' => '${mod}'`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//import createDebugLogger from 'debug';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import {fieldToString} from './utils.js';
|
|
4
4
|
|
|
5
5
|
// Author(s): Nicholas Volk
|
|
@@ -33,7 +33,7 @@ export default function () {
|
|
|
33
33
|
function validateField(field, res) {
|
|
34
34
|
const orig = fieldToString(field);
|
|
35
35
|
|
|
36
|
-
const normalizedField = fix505(
|
|
36
|
+
const normalizedField = fix505(structuredClone(field));
|
|
37
37
|
const mod = fieldToString(normalizedField);
|
|
38
38
|
if (orig !== mod) { // Fail as the input is "broken"/"crap"/sumthing
|
|
39
39
|
res.message.push(`'TODO: ${orig}' => '${mod}'`);
|
package/src/field-521-fix.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//import createDebugLogger from 'debug';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import {fieldToString} from './utils.js';
|
|
4
4
|
|
|
5
5
|
// Author(s): Nicholas Volk
|
|
@@ -40,7 +40,7 @@ export default function () {
|
|
|
40
40
|
function validateField(field, res) {
|
|
41
41
|
const orig = fieldToString(field);
|
|
42
42
|
|
|
43
|
-
const normalizedField = fix521(
|
|
43
|
+
const normalizedField = fix521(structuredClone(field));
|
|
44
44
|
const mod = fieldToString(normalizedField);
|
|
45
45
|
if (orig !== mod) { // Fail as the input is "broken"/"crap"/sumthing
|
|
46
46
|
res.message.push(`'TODO: ${orig}' => '${mod}'`);
|
package/src/fix-33X.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createDebugLogger from 'debug';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import {fieldToString, getCatalogingLanguage, nvdebug} from './utils.js';
|
|
4
4
|
import {map336CodeToTerm, map337CodeToTerm, map338CodeToTerm} from './field33XUtils.js';
|
|
5
5
|
|
|
@@ -398,7 +398,7 @@ export default function () {
|
|
|
398
398
|
return {message: [], valid: true};
|
|
399
399
|
}
|
|
400
400
|
const originalStrings = fields.map(f => fieldToString(f));
|
|
401
|
-
const clonedFields = fields.map(f =>
|
|
401
|
+
const clonedFields = fields.map(f => structuredClone(f));
|
|
402
402
|
clonedFields.forEach(f => fixField(f, catLang));
|
|
403
403
|
const modifiedStrings = clonedFields.map(f => fieldToString(f));
|
|
404
404
|
|