@natlibfi/marc-record-validators-melinda 12.0.15 → 12.0.16-alpha.2
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/fixRelatorTerms.js +2 -0
- package/dist/fixRelatorTerms.js.map +2 -2
- package/dist/normalizeFieldForComparison.js +16 -0
- package/dist/normalizeFieldForComparison.js.map +2 -2
- package/package.json +9 -12
- package/src/fixRelatorTerms.js +5 -0
- package/src/normalizeFieldForComparison.js +18 -0
- package/test-fixtures/fix-relator-terms/f02/metadata.json +1 -1
- package/test-fixtures/fix-relator-terms/f04/expectedResult.json +18 -0
- package/test-fixtures/fix-relator-terms/f04/metadata.json +5 -0
- package/test-fixtures/fix-relator-terms/f04/record.json +20 -0
- package/test-fixtures/merge-fields/f15/metadata.json +1 -1
- package/test-fixtures/merge-fields/f16/metadata.json +1 -1
- package/test-fixtures/merge-fields/f300_03/expectedResult.json +13 -0
- package/test-fixtures/merge-fields/f300_03/metadata.json +6 -0
- package/test-fixtures/merge-fields/f300_03/record.json +16 -0
- package/test-fixtures/remove-inferior-datafields/f20/metadata.json +1 -1
package/dist/fixRelatorTerms.js
CHANGED
|
@@ -43,6 +43,8 @@ const relatorTerms = [
|
|
|
43
43
|
{ "code": "drt", "eng": "director", "fin": "ohjaaja", "swe": "regiss\xF6r" },
|
|
44
44
|
{ "code": "edt", "eng": "editor", "fin": "toimittaja", "swe": "redakt\xF6r" },
|
|
45
45
|
{ "code": "ill", "eng": "illustrator", "fin": "kuvittaja", "swe": "illustrat\xF6r" },
|
|
46
|
+
{ "code": "ive", "eng": "interviewee", "fin": "haastateltava", "swe": "intervjuobjekt" },
|
|
47
|
+
{ "code": "ivr", "eng": "interviewer", "fin": "haastattelija", "swe": "intervjuare" },
|
|
46
48
|
{ "code": "lyr", "eng": "lyricist", "fin": "sanoittaja", "swe": "s\xE5ngtext" },
|
|
47
49
|
{ "code": "nrt", "eng": "narrator", "fin": "kertoja", "swe": "ber\xE4ttare" },
|
|
48
50
|
// berättare/inläsare
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/fixRelatorTerms.js"],
|
|
4
|
-
"sourcesContent": ["import clone from 'clone';\nimport {fieldFixPunctuation} from './punctuation2.js';\nimport {fieldToString, getCatalogingLanguage, nvdebug, subfieldToString} from './utils.js';\nimport createDebugLogger from 'debug';\n\n// Currently mainly translates X00$e values, so that we don't have \"$a Name, $e kirjoittaja, $e f\u00F6rfattare.\".\n// Later on we could try and handle $4 stuff here as well.\n\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:fixRelatorterms');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nexport default function () {\n return {\n description: 'Fix $e subfields in field [1678][01]0 and 720',\n validate, fix\n };\n\n function fix(record) {\n const res = {message: [], fix: [], valid: true};\n\n const language = getCatalogingLanguage(record);\n\n record.fields.forEach(field => {\n fieldFixRelatorTerms(field, language, language);\n });\n\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n const language = getCatalogingLanguage(record);\n\n record.fields.forEach(field => {\n const clonedField = clone(field);\n // Rather hackily/abnormally use language as both fromLanguage and toLanguage.\n // fromLanguage is used to expand \"esitt.\" => \"esitt\u00E4j\u00E4\".\n // toLanguage is used by translations (fixes \"f\u00F6rfattere\" to \"kirjoittaja\", if 040$b is \"fin\")\n fieldFixRelatorTerms(field, language, language);\n const clonedFieldAsString = fieldToString(clonedField);\n const fieldAsString = fieldToString(field);\n if (fieldAsString !== clonedFieldAsString) {\n res.message.push(`${fieldAsString} => ${clonedFieldAsString}`);\n }\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n}\n\n\n/*\nexport default () => (base, source) => {\n recordTranslateRelatorTerms(base);\n recordTranslateRelatorTerms(source);\n recordHandleRelatorTermAbbreviations(base);\n recordHandleRelatorTermAbbreviations(source);\n const result = {base, source};\n return result;\n};\n*/\n\n\n// Partial source: https://marc21.kansalliskirjasto.fi/funktiot_koodit.htm\n// https://wiki.helsinki.fi/display/MARC21svenska/Funktions-+och+relationskoder+-+alfabetiskt+efter+funktion\n// New, better source: https://id.kb.se/find?q=relator&_sort=_sortKeyByLang.en\n\n\n// NB! How to handle German sex-based Verfasser/Verfasserin pairs?\nconst relatorTerms = [\n {'code': 'arr', 'eng': 'arranger', 'fin': 'sovittaja', 'swe': 'arrang\u00F6r av musikalisk komposition'},\n {'code': 'art', 'eng': 'artist', 'fin': 'taiteilija', 'swe': 'konstn\u00E4r'},\n {'code': 'aui', 'eng': 'author of introduction', 'fin': 'esipuheen tekij\u00E4'},\n {'code': 'aut', 'eng': 'author', 'fin': 'kirjoittaja', 'swe': 'f\u00F6rfattare'},\n {'code': 'cmp', 'eng': 'composer', 'fin': 's\u00E4velt\u00E4j\u00E4', 'swe': 'komposit\u00F6r'},\n {'code': 'drt', 'eng': 'director', 'fin': 'ohjaaja', 'swe': 'regiss\u00F6r'},\n {'code': 'edt', 'eng': 'editor', 'fin': 'toimittaja', 'swe': 'redakt\u00F6r'},\n {'code': 'ill', 'eng': 'illustrator', 'fin': 'kuvittaja', 'swe': 'illustrat\u00F6r'},\n {'code': 'lyr', 'eng': 'lyricist', 'fin': 'sanoittaja', 'swe': 's\u00E5ngtext'},\n {'code': 'nrt', 'eng': 'narrator', 'fin': 'kertoja', 'swe': 'ber\u00E4ttare'}, // ber\u00E4ttare/inl\u00E4sare\n {'code': 'pbl', 'eng': 'publisher', 'fin': 'julkaisija', 'swe': 'utgivare'},\n {'code': 'pht', 'eng': 'photographer', 'fin': 'valokuvaaja', 'swe': 'fotograf'},\n {'code': 'prf', 'eng': 'performer', 'fin': 'esitt\u00E4j\u00E4', 'swe': 'framf\u00F6rande'},\n {'code': 'pro', 'eng': 'producer', 'fin': 'tuottaja', 'swe': 'producent'},\n {'code': 'trl', 'eng': 'translator', 'fin': 'k\u00E4\u00E4nt\u00E4j\u00E4', 'swe': '\u00F6vers\u00E4ttare'},\n {'code': '__FAKE_VALUE1__', 'fin': 'sarjakuvantekij\u00E4', 'swe': 'serieskapare'}\n];\n\n/*\nfunction recordNormalizeRelatorTerms(record, defaultLanguageCode = undef) {\n const languageCode = defaultLanguageCode ? defaultLanguageCode : getCatalogingLanguage(record);\n if (!languageCode || ['eng', 'fin', 'swe'].includes(languageCode)) {\n return;\n }\n\n}\n*/\n\n\nconst finnishAbbreviations = {\n 'esitt.': 'esitt\u00E4j\u00E4',\n 'k\u00E4\u00E4nt.': 'k\u00E4\u00E4nt\u00E4j\u00E4',\n 'n\u00E4ytt.': 'n\u00E4yttelij\u00E4',\n 'san.': 'sanoittaja',\n 'sov.': 'sovittaja',\n 's\u00E4v.': 's\u00E4velt\u00E4j\u00E4',\n 'toim.': 'toimittaja',\n // Quick and dirty implementation of https://github.com/NatLibFi/USEMARCON-BOOKWHERE-RDA/blob/master/bw_rda_kyril.rul#L651\n // As per M.I./Slavica\n '\u0445\u0443\u0434\u043E\u0436.': 'kuvittaja',\n '\u043F\u0435\u0440.': 'k\u00E4\u00E4nt\u00E4j\u00E4',\n '\u0441\u043E\u0441\u0442.': 'toimittaja', // might also be 'kokoaja'\n '\u0440\u0435\u0434.': 'toimittaja'\n};\n\nfunction subfieldHandleRelatorTermAbbreviation(subfield, language) {\n if (subfield.code !== 'e') {\n return;\n }\n nvdebug(`Relator cand subfield: '${subfieldToString(subfield)}', lang: ${language ? language : 'NULL'}`, debugDev);\n if (!language || language === 'mul') {\n subfieldHandleRelatorTermAbbreviation(subfield, 'fin');\n // Maybe later add Swedish here...\n return;\n }\n const value = subfield.value.replace(/,$/u, '');\n const punc = value === subfield.value ? '' : ',';\n\n const lcValue = value.toLowerCase(); // Check \u00C5, \u00C4, \u00D6...\n\n // NB: Policy: if no language or multi-language: apply all rules! (Not much overlap I hope...)\n if (!language || language === 'fin' || language === 'mul') {\n nvdebug(`Relator try Finnish for '${lcValue}}'...`, debugDev);\n if (lcValue in finnishAbbreviations) {\n const hit = `${finnishAbbreviations[lcValue]}${punc}`;\n nvdebug(`Relator hit: ${hit}`, debugDev);\n // NB! 'esitt.' => 'esitt\u00E4j\u00E4'\n subfield.value = hit;\n return;\n }\n }\n}\n\n\nfunction isRelatorField(field) {\n // Tag list might be incomplete!\n return field.tag.match(/^(?:100|110|600|610|700|710|720|800|810)$/u);\n}\n\nfunction fieldHandleRelatorTermAbbreviations(field, language) {\n if (!isRelatorField(field)) {\n return;\n }\n\n const originalValue = fieldToString(field);\n field.subfields.forEach(sf => subfieldHandleRelatorTermAbbreviation(sf, language));\n const modifiedValue = fieldToString(field);\n if (modifiedValue === originalValue) {\n return;\n }\n // Changes have happened... Try to punctuate.\n // (NB! We need punctuation as a module, if we are to make abbr expansion a marc-record-validators-melinda validator/fixer)\n fieldFixPunctuation(field);\n}\n\nfunction termIsInGivenLanguage(term, language) {\n return relatorTerms.some(row => language in row && row[language] === term);\n}\n\nfunction anyToLanguage(originalTerm) {\n // Sometimes there's no 040$b or 040$b and, say, 040$b and 700$e value don't correlate\n if (termIsInGivenLanguage(originalTerm, 'fin')) {\n return 'fin';\n }\n if (termIsInGivenLanguage(originalTerm, 'swe')) {\n return 'swe';\n }\n if (termIsInGivenLanguage(originalTerm, 'eng')) {\n return 'eng';\n }\n return null;\n}\n\nfunction translateRelatorTerm(originalTerm, defaultFromLanguage, toLanguage) {\n\n // originalTerm is supposed to be normal version (abbrs have been expanded), possibly with punctuation\n const term = originalTerm.replace(/[,.]$/u, '');\n nvdebug(`Try to translate '${term}' from ${defaultFromLanguage} to ${toLanguage}`, debugDev);\n\n // Kind of hacky... If term is in toLanguage, do nothing. defaultFromLanguage (040$b) isn't that reliable.\n if (termIsInGivenLanguage(term, toLanguage)) {\n return originalTerm;\n }\n // defaultFomLanguage (typically 040$b) isn't that reliable:\n const fromLanguage = defaultFromLanguage === null || !termIsInGivenLanguage(term, defaultFromLanguage) ? anyToLanguage(term) : defaultFromLanguage;\n\n const [candRow] = relatorTerms.filter(row => fromLanguage in row && toLanguage in row && row[fromLanguage] === term);\n if (candRow) {\n const punc = term === originalTerm ? '' : originalTerm.slice(-1);\n const translation = `${candRow[toLanguage]}${punc}`;\n nvdebug(`Translate relator term: ${originalTerm} => ${translation}`, debugDev);\n return translation;\n }\n return originalTerm;\n}\n\nfunction subfieldTranslateRelatorTerm(subfield, fromLanguage, toLanguage) {\n if (subfield.code !== 'e') {\n return;\n }\n subfield.value = translateRelatorTerm(subfield.value, fromLanguage, toLanguage);\n}\n\nexport function fieldFixRelatorTerms(field, fromLanguage, toLanguage) {\n // fromLanguage can not be relied upon.\n if (!isRelatorField(field)/* || fromLanguage === toLanguage*/) {\n return;\n }\n fieldHandleRelatorTermAbbreviations(field, fromLanguage);\n\n field.subfields.forEach(sf => subfieldTranslateRelatorTerm(sf, fromLanguage, toLanguage));\n}\n\n\nexport function recordFixRelatorTerms(record, defaultToLanguage = null, defaultFromLanguage = null) { // WAS: translateRecord()\n const fromLanguage = defaultFromLanguage ? defaultFromLanguage : getCatalogingLanguage(record);\n const toLanguage = defaultToLanguage ? defaultToLanguage : getCatalogingLanguage(record);\n\n record.fields.forEach(field => translateField(field, fromLanguage, toLanguage));\n\n function translateField(field, from, to) {\n fieldFixRelatorTerms(field, from, to);\n }\n}\n\n\n"],
|
|
5
|
-
"mappings": "AAAA,OAAO,WAAW;AAClB,SAAQ,2BAA0B;AAClC,SAAQ,eAAe,uBAAuB,SAAS,wBAAuB;AAC9E,OAAO,uBAAuB;AAM9B,MAAM,QAAQ,kBAAkB,0DAA0D;AAE1F,MAAM,WAAW,MAAM,OAAO,KAAK;AAEnC,0BAA2B;AACzB,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;AAE9C,UAAM,WAAW,sBAAsB,MAAM;AAE7C,WAAO,OAAO,QAAQ,WAAS;AAC7B,2BAAqB,OAAO,UAAU,QAAQ;AAAA,IAChD,CAAC;AAED,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,UAAM,WAAW,sBAAsB,MAAM;AAE7C,WAAO,OAAO,QAAQ,WAAS;AAC7B,YAAM,cAAc,MAAM,KAAK;AAI/B,2BAAqB,OAAO,UAAU,QAAQ;AAC9C,YAAM,sBAAsB,cAAc,WAAW;AACrD,YAAM,gBAAgB,cAAc,KAAK;AACzC,UAAI,kBAAkB,qBAAqB;AACzC,YAAI,QAAQ,KAAK,GAAG,aAAa,OAAO,mBAAmB,EAAE;AAAA,MAC/D;AAAA,IACF,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AACF;
|
|
4
|
+
"sourcesContent": ["import clone from 'clone';\nimport {fieldFixPunctuation} from './punctuation2.js';\nimport {fieldToString, getCatalogingLanguage, nvdebug, subfieldToString} from './utils.js';\nimport createDebugLogger from 'debug';\n\n// Currently mainly translates X00$e values, so that we don't have \"$a Name, $e kirjoittaja, $e f\u00F6rfattare.\".\n// Later on we could try and handle $4 stuff here as well.\n\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:fixRelatorterms');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nexport default function () {\n return {\n description: 'Fix $e subfields in field [1678][01]0 and 720',\n validate, fix\n };\n\n function fix(record) {\n const res = {message: [], fix: [], valid: true};\n\n const language = getCatalogingLanguage(record);\n\n record.fields.forEach(field => {\n fieldFixRelatorTerms(field, language, language);\n });\n\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n const language = getCatalogingLanguage(record);\n\n record.fields.forEach(field => {\n const clonedField = clone(field);\n // Rather hackily/abnormally use language as both fromLanguage and toLanguage.\n // fromLanguage is used to expand \"esitt.\" => \"esitt\u00E4j\u00E4\".\n // toLanguage is used by translations (fixes \"f\u00F6rfattere\" to \"kirjoittaja\", if 040$b is \"fin\")\n fieldFixRelatorTerms(field, language, language);\n const clonedFieldAsString = fieldToString(clonedField);\n const fieldAsString = fieldToString(field);\n if (fieldAsString !== clonedFieldAsString) {\n res.message.push(`${fieldAsString} => ${clonedFieldAsString}`);\n }\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n}\n\n\n/*\nexport default () => (base, source) => {\n recordTranslateRelatorTerms(base);\n recordTranslateRelatorTerms(source);\n recordHandleRelatorTermAbbreviations(base);\n recordHandleRelatorTermAbbreviations(source);\n const result = {base, source};\n return result;\n};\n*/\n\n\n// Partial source: https://marc21.kansalliskirjasto.fi/funktiot_koodit.htm\n// https://wiki.helsinki.fi/display/MARC21svenska/Funktions-+och+relationskoder+-+alfabetiskt+efter+funktion\n// New, better source: https://id.kb.se/find?q=relator&_sort=_sortKeyByLang.en\n\n// NOTE: There are problematic relatorterms that do not diffrentiate between agent+work vs agent+ekspression relation\n// ie. teoksen haastattelija vs haastattelija (ekspressio)\n// current solution ignores them and handles just \"plain\" relator terms\n\n// NB! How to handle German sex-based Verfasser/Verfasserin pairs?\nconst relatorTerms = [\n {'code': 'arr', 'eng': 'arranger', 'fin': 'sovittaja', 'swe': 'arrang\u00F6r av musikalisk komposition'},\n {'code': 'art', 'eng': 'artist', 'fin': 'taiteilija', 'swe': 'konstn\u00E4r'},\n {'code': 'aui', 'eng': 'author of introduction', 'fin': 'esipuheen tekij\u00E4'},\n {'code': 'aut', 'eng': 'author', 'fin': 'kirjoittaja', 'swe': 'f\u00F6rfattare'},\n {'code': 'cmp', 'eng': 'composer', 'fin': 's\u00E4velt\u00E4j\u00E4', 'swe': 'komposit\u00F6r'},\n {'code': 'drt', 'eng': 'director', 'fin': 'ohjaaja', 'swe': 'regiss\u00F6r'},\n {'code': 'edt', 'eng': 'editor', 'fin': 'toimittaja', 'swe': 'redakt\u00F6r'},\n {'code': 'ill', 'eng': 'illustrator', 'fin': 'kuvittaja', 'swe': 'illustrat\u00F6r'},\n {'code': 'ive', 'eng': 'interviewee', 'fin': 'haastateltava', 'swe': 'intervjuobjekt'},\n {'code': 'ivr', 'eng': 'interviewer', 'fin': 'haastattelija', 'swe': 'intervjuare'},\n {'code': 'lyr', 'eng': 'lyricist', 'fin': 'sanoittaja', 'swe': 's\u00E5ngtext'},\n {'code': 'nrt', 'eng': 'narrator', 'fin': 'kertoja', 'swe': 'ber\u00E4ttare'}, // ber\u00E4ttare/inl\u00E4sare\n {'code': 'pbl', 'eng': 'publisher', 'fin': 'julkaisija', 'swe': 'utgivare'},\n {'code': 'pht', 'eng': 'photographer', 'fin': 'valokuvaaja', 'swe': 'fotograf'},\n {'code': 'prf', 'eng': 'performer', 'fin': 'esitt\u00E4j\u00E4', 'swe': 'framf\u00F6rande'},\n {'code': 'pro', 'eng': 'producer', 'fin': 'tuottaja', 'swe': 'producent'},\n {'code': 'trl', 'eng': 'translator', 'fin': 'k\u00E4\u00E4nt\u00E4j\u00E4', 'swe': '\u00F6vers\u00E4ttare'},\n {'code': '__FAKE_VALUE1__', 'fin': 'sarjakuvantekij\u00E4', 'swe': 'serieskapare'}\n];\n\n/*\nfunction recordNormalizeRelatorTerms(record, defaultLanguageCode = undef) {\n const languageCode = defaultLanguageCode ? defaultLanguageCode : getCatalogingLanguage(record);\n if (!languageCode || ['eng', 'fin', 'swe'].includes(languageCode)) {\n return;\n }\n\n}\n*/\n\n\nconst finnishAbbreviations = {\n 'esitt.': 'esitt\u00E4j\u00E4',\n 'k\u00E4\u00E4nt.': 'k\u00E4\u00E4nt\u00E4j\u00E4',\n 'n\u00E4ytt.': 'n\u00E4yttelij\u00E4',\n 'san.': 'sanoittaja',\n 'sov.': 'sovittaja',\n 's\u00E4v.': 's\u00E4velt\u00E4j\u00E4',\n 'toim.': 'toimittaja',\n // Quick and dirty implementation of https://github.com/NatLibFi/USEMARCON-BOOKWHERE-RDA/blob/master/bw_rda_kyril.rul#L651\n // As per M.I./Slavica\n '\u0445\u0443\u0434\u043E\u0436.': 'kuvittaja',\n '\u043F\u0435\u0440.': 'k\u00E4\u00E4nt\u00E4j\u00E4',\n '\u0441\u043E\u0441\u0442.': 'toimittaja', // might also be 'kokoaja'\n '\u0440\u0435\u0434.': 'toimittaja'\n};\n\nfunction subfieldHandleRelatorTermAbbreviation(subfield, language) {\n if (subfield.code !== 'e') {\n return;\n }\n nvdebug(`Relator cand subfield: '${subfieldToString(subfield)}', lang: ${language ? language : 'NULL'}`, debugDev);\n if (!language || language === 'mul') {\n subfieldHandleRelatorTermAbbreviation(subfield, 'fin');\n // Maybe later add Swedish here...\n return;\n }\n const value = subfield.value.replace(/,$/u, '');\n const punc = value === subfield.value ? '' : ',';\n\n const lcValue = value.toLowerCase(); // Check \u00C5, \u00C4, \u00D6...\n\n // NB: Policy: if no language or multi-language: apply all rules! (Not much overlap I hope...)\n if (!language || language === 'fin' || language === 'mul') {\n nvdebug(`Relator try Finnish for '${lcValue}}'...`, debugDev);\n if (lcValue in finnishAbbreviations) {\n const hit = `${finnishAbbreviations[lcValue]}${punc}`;\n nvdebug(`Relator hit: ${hit}`, debugDev);\n // NB! 'esitt.' => 'esitt\u00E4j\u00E4'\n subfield.value = hit;\n return;\n }\n }\n}\n\n\nfunction isRelatorField(field) {\n // Tag list might be incomplete!\n return field.tag.match(/^(?:100|110|600|610|700|710|720|800|810)$/u);\n}\n\nfunction fieldHandleRelatorTermAbbreviations(field, language) {\n if (!isRelatorField(field)) {\n return;\n }\n\n const originalValue = fieldToString(field);\n field.subfields.forEach(sf => subfieldHandleRelatorTermAbbreviation(sf, language));\n const modifiedValue = fieldToString(field);\n if (modifiedValue === originalValue) {\n return;\n }\n // Changes have happened... Try to punctuate.\n // (NB! We need punctuation as a module, if we are to make abbr expansion a marc-record-validators-melinda validator/fixer)\n fieldFixPunctuation(field);\n}\n\nfunction termIsInGivenLanguage(term, language) {\n return relatorTerms.some(row => language in row && row[language] === term);\n}\n\nfunction anyToLanguage(originalTerm) {\n // Sometimes there's no 040$b or 040$b and, say, 040$b and 700$e value don't correlate\n if (termIsInGivenLanguage(originalTerm, 'fin')) {\n return 'fin';\n }\n if (termIsInGivenLanguage(originalTerm, 'swe')) {\n return 'swe';\n }\n if (termIsInGivenLanguage(originalTerm, 'eng')) {\n return 'eng';\n }\n return null;\n}\n\nfunction translateRelatorTerm(originalTerm, defaultFromLanguage, toLanguage) {\n\n // originalTerm is supposed to be normal version (abbrs have been expanded), possibly with punctuation\n const term = originalTerm.replace(/[,.]$/u, '');\n nvdebug(`Try to translate '${term}' from ${defaultFromLanguage} to ${toLanguage}`, debugDev);\n\n // Kind of hacky... If term is in toLanguage, do nothing. defaultFromLanguage (040$b) isn't that reliable.\n if (termIsInGivenLanguage(term, toLanguage)) {\n return originalTerm;\n }\n // defaultFomLanguage (typically 040$b) isn't that reliable:\n const fromLanguage = defaultFromLanguage === null || !termIsInGivenLanguage(term, defaultFromLanguage) ? anyToLanguage(term) : defaultFromLanguage;\n\n const [candRow] = relatorTerms.filter(row => fromLanguage in row && toLanguage in row && row[fromLanguage] === term);\n if (candRow) {\n const punc = term === originalTerm ? '' : originalTerm.slice(-1);\n const translation = `${candRow[toLanguage]}${punc}`;\n nvdebug(`Translate relator term: ${originalTerm} => ${translation}`, debugDev);\n return translation;\n }\n return originalTerm;\n}\n\nfunction subfieldTranslateRelatorTerm(subfield, fromLanguage, toLanguage) {\n if (subfield.code !== 'e') {\n return;\n }\n subfield.value = translateRelatorTerm(subfield.value, fromLanguage, toLanguage);\n}\n\nexport function fieldFixRelatorTerms(field, fromLanguage, toLanguage) {\n // fromLanguage can not be relied upon.\n if (!isRelatorField(field)/* || fromLanguage === toLanguage*/) {\n return;\n }\n fieldHandleRelatorTermAbbreviations(field, fromLanguage);\n\n field.subfields.forEach(sf => subfieldTranslateRelatorTerm(sf, fromLanguage, toLanguage));\n}\n\n\nexport function recordFixRelatorTerms(record, defaultToLanguage = null, defaultFromLanguage = null) { // WAS: translateRecord()\n const fromLanguage = defaultFromLanguage ? defaultFromLanguage : getCatalogingLanguage(record);\n const toLanguage = defaultToLanguage ? defaultToLanguage : getCatalogingLanguage(record);\n\n record.fields.forEach(field => translateField(field, fromLanguage, toLanguage));\n\n function translateField(field, from, to) {\n fieldFixRelatorTerms(field, from, to);\n }\n}\n\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -86,6 +86,21 @@ function fieldLowercase(field) {
|
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
function normalize300SubfieldA(field) {
|
|
90
|
+
if (field.tag !== "300") {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
field.subfields.forEach((sf) => normalizePages(sf));
|
|
94
|
+
function normalizePages(subfield) {
|
|
95
|
+
if (valuelessSubfield(subfield)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (subfield.code !== "a") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
subfield.value = subfield.value.replace(/(?:\b(?:pages?|pp\.|Seite|sida|sidor|sivu|sivua)\b|\bs$)/g, "s.");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
89
104
|
function hack490SubfieldA(field) {
|
|
90
105
|
if (field.tag !== "490") {
|
|
91
106
|
return;
|
|
@@ -136,6 +151,7 @@ function normalizeISBN(field) {
|
|
|
136
151
|
}
|
|
137
152
|
function fieldSpecificHacks(field) {
|
|
138
153
|
normalizeISBN(field);
|
|
154
|
+
normalize300SubfieldA(field);
|
|
139
155
|
hack490SubfieldA(field);
|
|
140
156
|
}
|
|
141
157
|
export function fieldTrimSubfieldValues(field) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/normalizeFieldForComparison.js"],
|
|
4
|
-
"sourcesContent": ["/*\n Note that this file contains very powerful normalizations and spells that are:\n - meant for comparing similarity/mergability of two fields (clone, normalize, compare),\n - and NOT for modifying the actual field!\n\n This is mainly used by melinda-marc-record-merge-reducers. However, also removeInferiorDataFields fixer also used this.\n Thus it is here. However, most of the testing is done via merge-reducers...\n*/\nimport clone from 'clone';\nimport {fieldStripPunctuation} from './punctuation2.js';\nimport {fieldToString, isContentSubfieldCode} from './utils.js';\n\nimport {fieldNormalizeControlNumbers/*, normalizeControlSubfieldValue*/} from './normalize-identifiers.js';\nimport createDebugLogger from 'debug';\nimport {normalizePartData, subfieldContainsPartData} from './normalizeSubfieldValueForComparison.js';\nimport {isEnnakkotietoSubfield} from './prepublicationUtils.js';\nimport {getSynonym} from './merge-fields/worldKnowledge.js';\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:normalizeFieldForComparison');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nfunction debugFieldComparison(oldField, newField) { // NB: Debug-only function!\n /*\n // We may drop certain subfields:\n if (oldField.subfields.length === newField.subfields.length) {\n oldField.subfields.forEach((subfield, index) => {\n const newValue = newField.subfields[index].value;\n if (subfield.value !== newValue) {\n nvdebug(`NORMALIZE SUBFIELD: '${subfield.value}' => '${newValue}'`, debugDev);\n }\n });\n }\n */\n const oldString = fieldToString(oldField);\n const newString = fieldToString(newField);\n if (oldString === newString) {\n return;\n }\n //nvdebug(`NORMALIZE FIELD:\\n '${fieldToString(oldField)}' =>\\n '${fieldToString(newField)}'`, debugDev);\n}\n\nfunction containsHumanName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 400... What else...\n if (['100', '600', '700', '800'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction containsCorporateName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 410... What else...\n if (['110', '610', '710', '810'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction skipAllSubfieldNormalizations(value, subfieldCode, tag) {\n\n if (isEnnakkotietoSubfield({'code': subfieldCode, value})) {\n return true;\n }\n\n if (tag === '035' && ['a', 'z'].includes(subfieldCode)) {\n return true;\n }\n\n if (!isContentSubfieldCode(subfieldCode, tag)) {\n return true;\n }\n return false;\n}\n\nfunction skipSubfieldLowercase(value, subfieldCode, tag) {\n // These may contain Roman Numerals...\n if (subfieldContainsPartData(tag, subfieldCode)) {\n return true;\n }\n\n return skipAllSubfieldNormalizations(value, subfieldCode, tag);\n}\n\nfunction skipAllFieldNormalizations(tag) {\n if (['LOW', 'SID'].includes(tag)) {\n return true;\n }\n return false;\n}\n\n\nfunction subfieldValueLowercase(value, subfieldCode, tag) {\n if (skipSubfieldLowercase(value, subfieldCode, tag)) {\n return value;\n }\n\n //return value.toLowerCase();\n const newValue = value.toLowerCase();\n if (newValue !== value) {\n //nvdebug(`SVL ${tag} $${subfieldCode} '${value}' =>`, debugDev);\n //nvdebug(`SVL ${tag} $${subfieldCode} '${newValue}'`, debugDev);\n return newValue;\n }\n return value;\n}\n\nfunction subfieldLowercase(sf, tag) {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = subfieldValueLowercase(sf.value, sf.code, tag);\n}\n\nfunction fieldLowercase(field) {\n if (skipFieldLowercase(field)) {\n return;\n }\n\n field.subfields.forEach(sf => subfieldLowercase(sf, field.tag));\n\n function skipFieldLowercase(field) {\n if (skipAllFieldNormalizations(field.tag)) {\n return true;\n }\n // Skip non-interesting fields\n if (!containsHumanName(field.tag) && !containsCorporateName(field.tag) && !['240', '245', '630'].includes(field.tag)) {\n return true;\n }\n\n return false;\n }\n}\n\n\nfunction hack490SubfieldA(field) {\n if (field.tag !== '490') {\n return;\n }\n field.subfields.forEach(sf => removeSarja(sf));\n\n // NB! This won't work, if the punctuation has not been stripped beforehand!\n function removeSarja(subfield) {\n if (valuelessSubfield(subfield)) {\n return;\n }\n\n if (subfield.code !== 'a') {\n return;\n }\n const tmp = subfield.value.replace(/ ?-(?:[a-z]|\u00E4|\u00F6)*sarja$/u, '');\n if (tmp.length > 0) {\n subfield.value = tmp;\n return;\n }\n }\n}\n\nexport function tagAndSubfieldCodeReferToIsbn(tag, subfieldCode) {\n // NB! We don't do this to 020$z!\n if (subfieldCode === 'z' && ['765', '767', '770', '772', '773', '774', '776', '777', '780', '785', '786', '787'].includes(tag)) {\n return true;\n }\n if (tag === '020' && subfieldCode === 'a') {\n return true;\n }\n return false;\n}\n\nfunction looksLikeIsbn(value) {\n // Does not check validity!\n if (value.match(/^(?:[0-9]-?){9}(?:[0-9]-?[0-9]-?[0-9]-?)?[0-9Xx]$/u)) {\n return true;\n }\n return false;\n}\n\nfunction normalizeISBN(field) {\n if (!field.subfields) {\n return;\n }\n\n //nvdebug(`ISBN-field? ${fieldToString(field)}`, debugDev);\n const relevantSubfields = field.subfields.filter(sf => tagAndSubfieldCodeReferToIsbn(field.tag, sf.code) && looksLikeIsbn(sf.value));\n relevantSubfields.forEach(sf => normalizeIsbnSubfield(sf));\n\n function normalizeIsbnSubfield(sf) {\n if (valuelessSubfield(sf)) {\n return;\n }\n //nvdebug(` ISBN-subfield? ${subfieldToString(sf)}`, debugDev);\n sf.value = sf.value.replace(/-/ug, '');\n sf.value = sf.value.replace(/x/u, 'X');\n }\n\n}\n\nfunction fieldSpecificHacks(field) {\n normalizeISBN(field); // 020$a, not $z!\n hack490SubfieldA(field);\n}\n\nexport function fieldTrimSubfieldValues(field) {\n field.subfields?.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = sf.value.replace(/^[ \\t\\n]+/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+$/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+/gu, ' ');\n });\n}\n\nfunction fieldRemoveDecomposedDiacritics(field) {\n // Raison d'\u00EAtre/motivation: \"Sir\u00E9n\" and diacriticless \"Siren\" might refer to a same surname, so this normalization\n // allows us to compare authors and avoid duplicate fields.\n field.subfields.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = removeDecomposedDiacritics(sf.value);\n });\n}\n\nfunction removeDecomposedDiacritics(value = '') {\n // NB #1: Does nothing to precomposed letters. Do String.normalize('NFD') first, if you want to handle them.\n // NB #2: Finnish letters '\u00E5', '\u00E4', '\u00F6', '\u00C5', \u00C4', and '\u00D6' should be handled (=precomposed) before calling this. (= keep them as is)\n // NB #3: Calling our very own fixComposition() before this function handles both #1 and #2.\n return String(value).replace(/\\p{Diacritic}/gu, '');\n}\n\nfunction normalizeSubfieldValue(value, subfieldCode, tag) {\n // NB! For comparison of values only\n /* beslint-disable */\n value = removeCharsThatDontCarryMeaning(value, tag, subfieldCode);\n value = getSynonym(tag, subfieldCode, value); // Must be done before punc stripping and lowercasing...\n value = subfieldValueLowercase(value, subfieldCode, tag);\n\n\n // Normalize: s. = sivut = pp.\n value = normalizePartData(value, subfieldCode, tag);\n value = value.replace(/^\\[([^[\\]]+)\\]/gu, '$1');\n\n if (['130', '730'].includes(tag) && subfieldCode === 'a') {\n value = value.replace(' : ', ', '); // \"Halloween ends (elokuva, 2022)\" vs \"Halloween ends (elokuva : 2023)\"\n }\n /* beslint-enable */\n\n // Not going to do these in the foreseeable future, but keeping them here for discussion:\n // Possible normalizations include but are not limited to:\n // \u00F8 => \u00F6? Might be language dependent: 041 $a fin => \u00F6, 041 $a eng => o?\n // \u00D8 => \u00D6?\n // \u00DF => ss\n // \u00FE => th (NB! Both upper and lower case)\n // ...\n // Probably nots:\n // \u00FC => y (probably not, though this correlates with Finnish letter-to-sound rules)\n // w => v (OK for Finnish sorting in certain cases, but we are not here, are we?)\n // I guess we should use decomposed values in code here. (Not sure what composition my examples above use.)\n return value;\n}\n\nexport function cloneAndRemovePunctuation(field) {\n const clonedField = clone(field);\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n fieldStripPunctuation(clonedField);\n fieldTrimSubfieldValues(clonedField);\n debugDev('PUNC');\n debugFieldComparison(field, clonedField);\n\n return clonedField;\n}\n\nfunction removeCharsThatDontCarryMeaning(value, tag, subfieldCode) {\n if (tag === '080') {\n return value;\n }\n\n // 3\" refers to inches, but as this is for comparison only we don't mind...\n value = value.replace(/['\u2018\u2019\"\u201E\u201C\u201D\u00AB\u00BB]/gu, ''); // MET-570 et al. Subset of https://hexdocs.pm/ex_unicode/Unicode.Category.QuoteMarks.html\n // MRA-273: Handle X00$a name initials.\n // NB #1: that we remove spaces for comparison (as it simpler), though actually space should be used. Doesn't matter as this is comparison only.\n // NB #2: we might/should eventually write a validator/fixer that adds those spaces. After that point, this expection should become obsolete.\n if (subfieldCode === 'a' && ['100', '400', '600', '700', '800'].includes(tag)) { // 400 is used in auth records. It's not a bib field at all.\n value = value.replace(/([A-Z]|\u00C5|\u00C4|\u00D6)\\. +/ugi, '$1.');\n }\n\n return value;\n}\n\nfunction normalizeField(field) {\n //sf.value = removeDecomposedDiacritics(sf.value);\n fieldStripPunctuation(field);\n fieldLowercase(field);\n fieldNormalizeControlNumbers(field); // FIN11 vs FI-MELINDA etc.\n return field;\n}\n\nexport function cloneAndNormalizeFieldForComparison(field) {\n // NB! This new field is for comparison purposes only.\n // Some of the normalizations might be considered a bit overkill for other purposes.\n const clonedField = cloneAndRemovePunctuation(field); // was only clone(field)\n\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n clonedField.subfields.forEach((sf) => { // Do this for all fields or some fields?\n if (valuelessSubfield(sf)) {\n return;\n }\n\n sf.value = normalizeSubfieldValue(sf.value, sf.code, field.tag);\n //sf.value = normalizeForSamenessCheck(field.tag, sf.code, sf.value);\n\n\n });\n\n normalizeField(clonedField);\n fieldRemoveDecomposedDiacritics(clonedField);\n fieldSpecificHacks(clonedField);\n fieldTrimSubfieldValues(clonedField);\n\n\n debugFieldComparison(field, clonedField); // For debugging purposes only\n\n return clonedField;\n}\n\nfunction fieldSkipNormalization(field) {\n if (!field.subfields || ['018', '066', '080', '083'].includes(field.tag)) {\n return true;\n }\n return false;\n}\n\nfunction valuelessSubfield(sf) {\n return sf.value === undefined;\n}"],
|
|
5
|
-
"mappings": "AAQA,OAAO,WAAW;AAClB,SAAQ,6BAA4B;AACpC,SAAQ,eAAe,6BAA4B;AAEnD,SAAQ,oCAAsE;AAC9E,OAAO,uBAAuB;AAC9B,SAAQ,mBAAmB,gCAA+B;AAC1D,SAAQ,8BAA6B;AACrC,SAAQ,kBAAiB;AAEzB,MAAM,QAAQ,kBAAkB,0EAA0E;AAE1G,MAAM,WAAW,MAAM,OAAO,KAAK;AAEnC,SAAS,qBAAqB,UAAU,UAAU;AAYhD,QAAM,YAAY,cAAc,QAAQ;AACxC,QAAM,YAAY,cAAc,QAAQ;AACxC,MAAI,cAAc,WAAW;AAC3B;AAAA,EACF;AAEF;AAEA,SAAS,kBAAkB,MAAM,OAAO,eAAe,QAAW;AAEhE,MAAI,CAAC,OAAO,OAAO,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG;AAC9C,QAAI,iBAAiB,UAAa,iBAAiB,KAAK;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,sBAAsB,MAAM,OAAO,eAAe,QAAW;AAEpE,MAAI,CAAC,OAAO,OAAO,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG;AAC9C,QAAI,iBAAiB,UAAa,iBAAiB,KAAK;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,8BAA8B,OAAO,cAAc,KAAK;AAE/D,MAAI,uBAAuB,EAAC,QAAQ,cAAc,MAAK,CAAC,GAAG;AACzD,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,SAAS,CAAC,KAAK,GAAG,EAAE,SAAS,YAAY,GAAG;AACtD,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,sBAAsB,cAAc,GAAG,GAAG;AAC7C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,OAAO,cAAc,KAAK;AAEvD,MAAI,yBAAyB,KAAK,YAAY,GAAG;AAC/C,WAAO;AAAA,EACT;AAEA,SAAO,8BAA8B,OAAO,cAAc,GAAG;AAC/D;AAEA,SAAS,2BAA2B,KAAK;AACvC,MAAI,CAAC,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG;AAChC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,uBAAuB,OAAO,cAAc,KAAK;AACxD,MAAI,sBAAsB,OAAO,cAAc,GAAG,GAAG;AACnD,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,MAAM,YAAY;AACnC,MAAI,aAAa,OAAO;AAGtB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,IAAI,KAAK;AAClC,MAAI,kBAAkB,EAAE,GAAG;AACzB;AAAA,EACF;AACA,KAAG,QAAQ,uBAAuB,GAAG,OAAO,GAAG,MAAM,GAAG;AAC1D;AAEA,SAAS,eAAe,OAAO;AAC7B,MAAI,mBAAmB,KAAK,GAAG;AAC7B;AAAA,EACF;AAEA,QAAM,UAAU,QAAQ,QAAM,kBAAkB,IAAI,MAAM,GAAG,CAAC;AAE9D,WAAS,mBAAmBA,QAAO;AACjC,QAAI,2BAA2BA,OAAM,GAAG,GAAG;AACzC,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,kBAAkBA,OAAM,GAAG,KAAK,CAAC,sBAAsBA,OAAM,GAAG,KAAK,CAAC,CAAC,OAAO,OAAO,KAAK,EAAE,SAASA,OAAM,GAAG,GAAG;AACpH,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;
|
|
4
|
+
"sourcesContent": ["/*\n Note that this file contains very powerful normalizations and spells that are:\n - meant for comparing similarity/mergability of two fields (clone, normalize, compare),\n - and NOT for modifying the actual field!\n\n This is mainly used by melinda-marc-record-merge-reducers. However, also removeInferiorDataFields fixer also used this.\n Thus it is here. However, most of the testing is done via merge-reducers...\n*/\nimport clone from 'clone';\nimport {fieldStripPunctuation} from './punctuation2.js';\nimport {fieldToString, isContentSubfieldCode} from './utils.js';\n\nimport {fieldNormalizeControlNumbers/*, normalizeControlSubfieldValue*/} from './normalize-identifiers.js';\nimport createDebugLogger from 'debug';\nimport {normalizePartData, subfieldContainsPartData} from './normalizeSubfieldValueForComparison.js';\nimport {isEnnakkotietoSubfield} from './prepublicationUtils.js';\nimport {getSynonym} from './merge-fields/worldKnowledge.js';\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:normalizeFieldForComparison');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nfunction debugFieldComparison(oldField, newField) { // NB: Debug-only function!\n /*\n // We may drop certain subfields:\n if (oldField.subfields.length === newField.subfields.length) {\n oldField.subfields.forEach((subfield, index) => {\n const newValue = newField.subfields[index].value;\n if (subfield.value !== newValue) {\n nvdebug(`NORMALIZE SUBFIELD: '${subfield.value}' => '${newValue}'`, debugDev);\n }\n });\n }\n */\n const oldString = fieldToString(oldField);\n const newString = fieldToString(newField);\n if (oldString === newString) {\n return;\n }\n //nvdebug(`NORMALIZE FIELD:\\n '${fieldToString(oldField)}' =>\\n '${fieldToString(newField)}'`, debugDev);\n}\n\nfunction containsHumanName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 400... What else...\n if (['100', '600', '700', '800'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction containsCorporateName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 410... What else...\n if (['110', '610', '710', '810'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction skipAllSubfieldNormalizations(value, subfieldCode, tag) {\n\n if (isEnnakkotietoSubfield({'code': subfieldCode, value})) {\n return true;\n }\n\n if (tag === '035' && ['a', 'z'].includes(subfieldCode)) {\n return true;\n }\n\n if (!isContentSubfieldCode(subfieldCode, tag)) {\n return true;\n }\n return false;\n}\n\nfunction skipSubfieldLowercase(value, subfieldCode, tag) {\n // These may contain Roman Numerals...\n if (subfieldContainsPartData(tag, subfieldCode)) {\n return true;\n }\n\n return skipAllSubfieldNormalizations(value, subfieldCode, tag);\n}\n\nfunction skipAllFieldNormalizations(tag) {\n if (['LOW', 'SID'].includes(tag)) {\n return true;\n }\n return false;\n}\n\n\nfunction subfieldValueLowercase(value, subfieldCode, tag) {\n if (skipSubfieldLowercase(value, subfieldCode, tag)) {\n return value;\n }\n\n //return value.toLowerCase();\n const newValue = value.toLowerCase();\n if (newValue !== value) {\n //nvdebug(`SVL ${tag} $${subfieldCode} '${value}' =>`, debugDev);\n //nvdebug(`SVL ${tag} $${subfieldCode} '${newValue}'`, debugDev);\n return newValue;\n }\n return value;\n}\n\nfunction subfieldLowercase(sf, tag) {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = subfieldValueLowercase(sf.value, sf.code, tag);\n}\n\nfunction fieldLowercase(field) {\n if (skipFieldLowercase(field)) {\n return;\n }\n\n field.subfields.forEach(sf => subfieldLowercase(sf, field.tag));\n\n function skipFieldLowercase(field) {\n if (skipAllFieldNormalizations(field.tag)) {\n return true;\n }\n // Skip non-interesting fields\n if (!containsHumanName(field.tag) && !containsCorporateName(field.tag) && !['240', '245', '630'].includes(field.tag)) {\n return true;\n }\n\n return false;\n }\n}\n\nfunction normalize300SubfieldA(field) {\n if (field.tag !== '300') {\n return;\n }\n field.subfields.forEach(sf => normalizePages(sf));\n\n function normalizePages(subfield) {\n if (valuelessSubfield(subfield)) {\n return;\n }\n\n if (subfield.code !== 'a') {\n return;\n }\n subfield.value = subfield.value.replace(/(?:\\b(?:pages?|pp\\.|Seite|sida|sidor|sivu|sivua)\\b|\\bs$)/g, 's.');\n }\n}\n\nfunction hack490SubfieldA(field) {\n if (field.tag !== '490') {\n return;\n }\n field.subfields.forEach(sf => removeSarja(sf));\n\n // NB! This won't work, if the punctuation has not been stripped beforehand!\n function removeSarja(subfield) {\n if (valuelessSubfield(subfield)) {\n return;\n }\n\n if (subfield.code !== 'a') {\n return;\n }\n const tmp = subfield.value.replace(/ ?-(?:[a-z]|\u00E4|\u00F6)*sarja$/u, '');\n if (tmp.length > 0) {\n subfield.value = tmp;\n return;\n }\n }\n}\n\nexport function tagAndSubfieldCodeReferToIsbn(tag, subfieldCode) {\n // NB! We don't do this to 020$z!\n if (subfieldCode === 'z' && ['765', '767', '770', '772', '773', '774', '776', '777', '780', '785', '786', '787'].includes(tag)) {\n return true;\n }\n if (tag === '020' && subfieldCode === 'a') {\n return true;\n }\n return false;\n}\n\nfunction looksLikeIsbn(value) {\n // Does not check validity!\n if (value.match(/^(?:[0-9]-?){9}(?:[0-9]-?[0-9]-?[0-9]-?)?[0-9Xx]$/u)) {\n return true;\n }\n return false;\n}\n\nfunction normalizeISBN(field) {\n if (!field.subfields) {\n return;\n }\n\n //nvdebug(`ISBN-field? ${fieldToString(field)}`, debugDev);\n const relevantSubfields = field.subfields.filter(sf => tagAndSubfieldCodeReferToIsbn(field.tag, sf.code) && looksLikeIsbn(sf.value));\n relevantSubfields.forEach(sf => normalizeIsbnSubfield(sf));\n\n function normalizeIsbnSubfield(sf) {\n if (valuelessSubfield(sf)) {\n return;\n }\n //nvdebug(` ISBN-subfield? ${subfieldToString(sf)}`, debugDev);\n sf.value = sf.value.replace(/-/ug, '');\n sf.value = sf.value.replace(/x/u, 'X');\n }\n\n}\n\nfunction fieldSpecificHacks(field) {\n normalizeISBN(field); // 020$a, not $z!\n normalize300SubfieldA(field);\n hack490SubfieldA(field);\n}\n\nexport function fieldTrimSubfieldValues(field) {\n field.subfields?.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = sf.value.replace(/^[ \\t\\n]+/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+$/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+/gu, ' ');\n });\n}\n\nfunction fieldRemoveDecomposedDiacritics(field) {\n // Raison d'\u00EAtre/motivation: \"Sir\u00E9n\" and diacriticless \"Siren\" might refer to a same surname, so this normalization\n // allows us to compare authors and avoid duplicate fields.\n field.subfields.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = removeDecomposedDiacritics(sf.value);\n });\n}\n\nfunction removeDecomposedDiacritics(value = '') {\n // NB #1: Does nothing to precomposed letters. Do String.normalize('NFD') first, if you want to handle them.\n // NB #2: Finnish letters '\u00E5', '\u00E4', '\u00F6', '\u00C5', \u00C4', and '\u00D6' should be handled (=precomposed) before calling this. (= keep them as is)\n // NB #3: Calling our very own fixComposition() before this function handles both #1 and #2.\n return String(value).replace(/\\p{Diacritic}/gu, '');\n}\n\nfunction normalizeSubfieldValue(value, subfieldCode, tag) {\n // NB! For comparison of values only\n /* beslint-disable */\n value = removeCharsThatDontCarryMeaning(value, tag, subfieldCode);\n value = getSynonym(tag, subfieldCode, value); // Must be done before punc stripping and lowercasing...\n value = subfieldValueLowercase(value, subfieldCode, tag);\n\n\n // Normalize: s. = sivut = pp.\n value = normalizePartData(value, subfieldCode, tag);\n value = value.replace(/^\\[([^[\\]]+)\\]/gu, '$1');\n\n if (['130', '730'].includes(tag) && subfieldCode === 'a') {\n value = value.replace(' : ', ', '); // \"Halloween ends (elokuva, 2022)\" vs \"Halloween ends (elokuva : 2023)\"\n }\n /* beslint-enable */\n\n // Not going to do these in the foreseeable future, but keeping them here for discussion:\n // Possible normalizations include but are not limited to:\n // \u00F8 => \u00F6? Might be language dependent: 041 $a fin => \u00F6, 041 $a eng => o?\n // \u00D8 => \u00D6?\n // \u00DF => ss\n // \u00FE => th (NB! Both upper and lower case)\n // ...\n // Probably nots:\n // \u00FC => y (probably not, though this correlates with Finnish letter-to-sound rules)\n // w => v (OK for Finnish sorting in certain cases, but we are not here, are we?)\n // I guess we should use decomposed values in code here. (Not sure what composition my examples above use.)\n return value;\n}\n\nexport function cloneAndRemovePunctuation(field) {\n const clonedField = clone(field);\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n fieldStripPunctuation(clonedField);\n fieldTrimSubfieldValues(clonedField);\n debugDev('PUNC');\n debugFieldComparison(field, clonedField);\n\n return clonedField;\n}\n\nfunction removeCharsThatDontCarryMeaning(value, tag, subfieldCode) {\n if (tag === '080') {\n return value;\n }\n\n // 3\" refers to inches, but as this is for comparison only we don't mind...\n value = value.replace(/['\u2018\u2019\"\u201E\u201C\u201D\u00AB\u00BB]/gu, ''); // MET-570 et al. Subset of https://hexdocs.pm/ex_unicode/Unicode.Category.QuoteMarks.html\n // MRA-273: Handle X00$a name initials.\n // NB #1: that we remove spaces for comparison (as it simpler), though actually space should be used. Doesn't matter as this is comparison only.\n // NB #2: we might/should eventually write a validator/fixer that adds those spaces. After that point, this expection should become obsolete.\n if (subfieldCode === 'a' && ['100', '400', '600', '700', '800'].includes(tag)) { // 400 is used in auth records. It's not a bib field at all.\n value = value.replace(/([A-Z]|\u00C5|\u00C4|\u00D6)\\. +/ugi, '$1.');\n }\n\n return value;\n}\n\nfunction normalizeField(field) {\n //sf.value = removeDecomposedDiacritics(sf.value);\n fieldStripPunctuation(field);\n fieldLowercase(field);\n fieldNormalizeControlNumbers(field); // FIN11 vs FI-MELINDA etc.\n return field;\n}\n\nexport function cloneAndNormalizeFieldForComparison(field) {\n // NB! This new field is for comparison purposes only.\n // Some of the normalizations might be considered a bit overkill for other purposes.\n const clonedField = cloneAndRemovePunctuation(field); // was only clone(field)\n\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n clonedField.subfields.forEach((sf) => { // Do this for all fields or some fields?\n if (valuelessSubfield(sf)) {\n return;\n }\n\n sf.value = normalizeSubfieldValue(sf.value, sf.code, field.tag);\n //sf.value = normalizeForSamenessCheck(field.tag, sf.code, sf.value);\n\n\n });\n\n normalizeField(clonedField);\n fieldRemoveDecomposedDiacritics(clonedField);\n fieldSpecificHacks(clonedField);\n fieldTrimSubfieldValues(clonedField);\n\n\n debugFieldComparison(field, clonedField); // For debugging purposes only\n\n return clonedField;\n}\n\nfunction fieldSkipNormalization(field) {\n if (!field.subfields || ['018', '066', '080', '083'].includes(field.tag)) {\n return true;\n }\n return false;\n}\n\nfunction valuelessSubfield(sf) {\n return sf.value === undefined;\n}"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["field"]
|
|
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": "12.0.
|
|
17
|
+
"version": "12.0.16-alpha.2",
|
|
18
18
|
"main": "./dist/index.js",
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
@@ -35,30 +35,27 @@
|
|
|
35
35
|
"dev:debug": "cross-env LOG_LEVEL=debug DEBUG=@natlibfi/* NODE_ENV=test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@natlibfi/iso9-1995": "^1.1.
|
|
39
|
-
"@natlibfi/issn-verify": "^2.0.
|
|
40
|
-
"@natlibfi/marc-record": "^10.0.
|
|
41
|
-
"@natlibfi/marc-record-serializers": "^11.0.
|
|
42
|
-
"@natlibfi/marc-record-validate": "^9.0.
|
|
38
|
+
"@natlibfi/iso9-1995": "^1.1.4",
|
|
39
|
+
"@natlibfi/issn-verify": "^2.0.3",
|
|
40
|
+
"@natlibfi/marc-record": "^10.0.2",
|
|
41
|
+
"@natlibfi/marc-record-serializers": "^11.0.3",
|
|
42
|
+
"@natlibfi/marc-record-validate": "^9.0.1",
|
|
43
43
|
"@natlibfi/melinda-commons": "^14.0.2",
|
|
44
44
|
"@natlibfi/sfs-4900": "^2.0.0",
|
|
45
|
-
"@natlibfi/sru-client": "^7.0.
|
|
45
|
+
"@natlibfi/sru-client": "^7.0.2",
|
|
46
46
|
"clone": "^2.1.2",
|
|
47
47
|
"debug": "^4.4.3",
|
|
48
48
|
"franc": "^6.2.0",
|
|
49
|
-
"isbn3": "^2.0.
|
|
49
|
+
"isbn3": "^2.0.9",
|
|
50
50
|
"xml2js": "^0.6.2",
|
|
51
51
|
"xregexp": "^5.1.2"
|
|
52
52
|
},
|
|
53
|
-
"peerDependencies": {
|
|
54
|
-
"@natlibfi/marc-record-validate": "^9.0.0"
|
|
55
|
-
},
|
|
56
53
|
"devDependencies": {
|
|
57
54
|
"@natlibfi/fixugen": "^3.0.1",
|
|
58
55
|
"@natlibfi/fixura": "^4.0.1",
|
|
59
56
|
"cross-env": "^10.1.0",
|
|
60
57
|
"esbuild": "^0.28.0",
|
|
61
|
-
"eslint": "^10.
|
|
58
|
+
"eslint": "^10.7.0",
|
|
62
59
|
"fetch-mock": "^12.6.0"
|
|
63
60
|
}
|
|
64
61
|
}
|
package/src/fixRelatorTerms.js
CHANGED
|
@@ -69,6 +69,9 @@ export default () => (base, source) => {
|
|
|
69
69
|
// https://wiki.helsinki.fi/display/MARC21svenska/Funktions-+och+relationskoder+-+alfabetiskt+efter+funktion
|
|
70
70
|
// New, better source: https://id.kb.se/find?q=relator&_sort=_sortKeyByLang.en
|
|
71
71
|
|
|
72
|
+
// NOTE: There are problematic relatorterms that do not diffrentiate between agent+work vs agent+ekspression relation
|
|
73
|
+
// ie. teoksen haastattelija vs haastattelija (ekspressio)
|
|
74
|
+
// current solution ignores them and handles just "plain" relator terms
|
|
72
75
|
|
|
73
76
|
// NB! How to handle German sex-based Verfasser/Verfasserin pairs?
|
|
74
77
|
const relatorTerms = [
|
|
@@ -80,6 +83,8 @@ const relatorTerms = [
|
|
|
80
83
|
{'code': 'drt', 'eng': 'director', 'fin': 'ohjaaja', 'swe': 'regissör'},
|
|
81
84
|
{'code': 'edt', 'eng': 'editor', 'fin': 'toimittaja', 'swe': 'redaktör'},
|
|
82
85
|
{'code': 'ill', 'eng': 'illustrator', 'fin': 'kuvittaja', 'swe': 'illustratör'},
|
|
86
|
+
{'code': 'ive', 'eng': 'interviewee', 'fin': 'haastateltava', 'swe': 'intervjuobjekt'},
|
|
87
|
+
{'code': 'ivr', 'eng': 'interviewer', 'fin': 'haastattelija', 'swe': 'intervjuare'},
|
|
83
88
|
{'code': 'lyr', 'eng': 'lyricist', 'fin': 'sanoittaja', 'swe': 'sångtext'},
|
|
84
89
|
{'code': 'nrt', 'eng': 'narrator', 'fin': 'kertoja', 'swe': 'berättare'}, // berättare/inläsare
|
|
85
90
|
{'code': 'pbl', 'eng': 'publisher', 'fin': 'julkaisija', 'swe': 'utgivare'},
|
|
@@ -137,6 +137,23 @@ function fieldLowercase(field) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
function normalize300SubfieldA(field) {
|
|
141
|
+
if (field.tag !== '300') {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
field.subfields.forEach(sf => normalizePages(sf));
|
|
145
|
+
|
|
146
|
+
function normalizePages(subfield) {
|
|
147
|
+
if (valuelessSubfield(subfield)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (subfield.code !== 'a') {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
subfield.value = subfield.value.replace(/(?:\b(?:pages?|pp\.|Seite|sida|sidor|sivu|sivua)\b|\bs$)/g, 's.');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
140
157
|
|
|
141
158
|
function hack490SubfieldA(field) {
|
|
142
159
|
if (field.tag !== '490') {
|
|
@@ -202,6 +219,7 @@ function normalizeISBN(field) {
|
|
|
202
219
|
|
|
203
220
|
function fieldSpecificHacks(field) {
|
|
204
221
|
normalizeISBN(field); // 020$a, not $z!
|
|
222
|
+
normalize300SubfieldA(field);
|
|
205
223
|
hack490SubfieldA(field);
|
|
206
224
|
}
|
|
207
225
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_validationOptions": {},
|
|
3
|
+
"leader": "01331cam a22003494i 4500",
|
|
4
|
+
"fields": [
|
|
5
|
+
{ "tag": "040", "ind1": " ", "ind2": " ", "subfields": [ { "code": "b", "value": "fin" } ] },
|
|
6
|
+
{ "tag": "700", "ind1": " ", "ind2": " ",
|
|
7
|
+
"subfields": [
|
|
8
|
+
{ "code": "a", "value": "Nimi," },
|
|
9
|
+
{ "code": "e", "value": "kirjoittaja,"},
|
|
10
|
+
{ "code": "e", "value": "haastattelija."}
|
|
11
|
+
] },
|
|
12
|
+
{ "tag": "700", "ind1": " ", "ind2": " ",
|
|
13
|
+
"subfields": [
|
|
14
|
+
{ "code": "a", "value": "Nimi2," },
|
|
15
|
+
{ "code": "e", "value": "haastateltava."}
|
|
16
|
+
]}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"leader": "01331cam a22003494i 4500",
|
|
3
|
+
"fields": [
|
|
4
|
+
{ "tag": "040", "ind1": " ", "ind2": " ", "subfields": [ { "code": "b", "value": "fin" } ] },
|
|
5
|
+
{ "tag": "700", "ind1": " ", "ind2": " ",
|
|
6
|
+
"subfields": [
|
|
7
|
+
{ "code": "a", "value": "Nimi," },
|
|
8
|
+
{ "code": "e", "value": "författare,"},
|
|
9
|
+
{ "code": "e", "value": "intervjuare."}
|
|
10
|
+
] },
|
|
11
|
+
{ "tag": "700", "ind1": " ", "ind2": " ",
|
|
12
|
+
"subfields": [
|
|
13
|
+
{ "code": "a", "value": "Nimi2," },
|
|
14
|
+
{ "code": "e", "value": "intervjuobjekt."}
|
|
15
|
+
] }
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"leader": "01331cam a22003494i 4500",
|
|
3
|
+
"fields": [
|
|
4
|
+
{ "tag": "300", "ind1": " ", "ind2": " ", "subfields": [
|
|
5
|
+
{ "code": "a", "value": "205 sivua :" },
|
|
6
|
+
{ "code": "b", "value": "kuvitettu ;" },
|
|
7
|
+
{ "code": "c", "value": "30 cm" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
|
|
12
|
+
"_validationOptions": {}
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"leader": "01331cam a22003494i 4500",
|
|
3
|
+
"fields": [
|
|
4
|
+
{ "tag": "300", "ind1": " ", "ind2": " ", "subfields": [
|
|
5
|
+
{ "code": "a", "value": "205 sivua :" },
|
|
6
|
+
{ "code": "c", "value": "30 cm" }
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
{ "tag": "300", "ind1": " ", "ind2": " ", "subfields": [
|
|
10
|
+
{ "code": "a", "value": "205 s. :" },
|
|
11
|
+
{ "code": "b", "value": "kuvitettu" }
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"_validationOptions": {}
|
|
16
|
+
}
|