@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/modernize-502.js"],
|
|
4
|
-
"sourcesContent": ["//import createDebugLogger from 'debug';\nimport
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["//import createDebugLogger from 'debug';\n\nimport {fieldToString} from './utils.js';\n\n//const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda/modernize-502');\n\n// Author(s): Nicholas Volk\nexport default function () {\n\n return {\n description: 'Normalizes Finnish national convention 502$a$c$d fields to a 502$a (which is better supported by LoC).',\n validate, fix\n };\n\n function fix(record) {\n const res = {message: [], fix: [], valid: true};\n\n record.fields.forEach(field => {\n normalizeField502(field);\n });\n\n // message.valid = !(message.message.length >= 1);\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n record.fields.forEach(field => {\n validateField(field, res);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res) {\n if (!field.subfields) {\n return;\n }\n const orig = fieldToString(field);\n\n const normalizedField = normalizeField502(structuredClone(field));\n const mod = fieldToString(normalizedField);\n if (orig !== mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n res.message.push(`Fix '${orig}' => '${mod}'`);\n return;\n }\n return;\n }\n}\n\n\nexport function normalizeField502(field) {\n if (!field.subfields) {\n return field;\n }\n const acd = field.subfields.filter(sf => 'acd'.includes(sf.code));\n const str = acd.map(sf => sf.code).join('');\n // Check that we have relevant subfields and that they are in relevant order (with each other):\n if (!['acd', 'ac', 'ad'].includes(str)) {\n return field;\n }\n\n // \"a = acd[0]\"\" is way more readable than \"[a] = acd\"...\n const a = acd[0];\n const c = acd[1].code === 'c' ? acd[1] : null;\n const d = acd[acd.length - 1].code === 'd' ? acd[acd.length - 1] : null;\n\n //console.log(JSON.stringify(d)); // eslint-disable-line no-console\n\n if (!hasValidA() || !hasValidD()) {\n return field;\n }\n\n const newValue = `${extractA()}--${extractC()}${extractD()}`;\n a.value = newValue;\n field.subfields = field.subfields.filter(sf => !['c', 'd'].includes(sf.code));\n return field;\n\n function extractA() {\n return a.value.replace(/[ ,:]+$/u, '');\n }\n\n function extractC() {\n if (!c) {\n return '';\n }\n if (c) {\n // Here we assume that there was correct punctuation between $c and $d...\n return `${c.value}${d ? ' ' : ''}`;\n }\n return c.value;\n }\n\n function extractD() {\n return d ? d.value : '';\n }\n\n function hasValidA() {\n // Belongs to https://finto.fi/mts/fi/page/m91\n if (a.value.match(/^(?:AMK-opinn\u00E4ytety\u00F6|Anbalyysiseminaarity\u00F6|Artikkeliv\u00E4it\u00F6skirja|Diplomity\u00F6|Erikoisty\u00F6|Esseev\u00E4it\u00F6skirja|Kandidaatintutkielma|Laudaturseminaarity\u00F6|Laudaturty\u00F6|Lisensiaatinty\u00F6|Lopputy\u00F6|Monografiav\u00E4it\u00F6skirja|Opinn\u00E4yte|Opinn\u00E4ytety\u00F6|Pro gradu -tutkielma|Proseminaarity\u00F6|Seminaarity\u00F6|V\u00E4it\u00F6skirja|Ylempi AMK-opinn\u00E4ytety\u00F6)[, :]*$/u) ||\n a.value.match(/^(?:Analysseminariearbete|Artikelavhandling|Diplomarbete|Doktorsavhandling|Ess\u00E4avhandling|H\u00F6gre YH-examensarbete|Kandidatavhandling|Laudaturarbete|Laudaturseminariearbete|Licentiatavhandling|L\u00E4rdomsprov|Monografiavhandling|Pro gradu-avhandling|Proseminariearbete|Seminariearbete|Slutarbete|Specialarbete|YH-examesarbete)[:, ]*$/u)) {\n return true;\n }\n return false;\n }\n\n\n function hasValidD() {\n if (!d) { // We can live without $d:\n return true;\n }\n // Content makes sense:\n return d.value.match(/^\\[?(?:[0-9]{4}|[0-9]{4}-[0-9]{4}|1[89]uu|Vuosien [0-9]{4} ja [0-9]{4} v\u00E4lill\u00E4)[\\].]{0,2}$/u);\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;AAE9C,WAAO,OAAO,QAAQ,WAAS;AAC7B,wBAAkB,KAAK;AAAA,IACzB,CAAC;AAGD,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,WAAO,OAAO,QAAQ,WAAS;AAC7B,oBAAc,OAAO,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK;AACjC,QAAI,CAAC,MAAM,WAAW;AACpB;AAAA,IACF;AACA,UAAM,OAAO,cAAc,KAAK;AAEhC,UAAM,kBAAkB,kBAAkB,gBAAgB,KAAK,CAAC;AAChE,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB,UAAI,QAAQ,KAAK,QAAQ,IAAI,SAAS,GAAG,GAAG;AAC5C;AAAA,IACF;AACA;AAAA,EACF;AACF;AAGO,gBAAS,kBAAkB,OAAO;AACvC,MAAI,CAAC,MAAM,WAAW;AACpB,WAAO;AAAA,EACT;AACA,QAAM,MAAM,MAAM,UAAU,OAAO,QAAM,MAAM,SAAS,GAAG,IAAI,CAAC;AAChE,QAAM,MAAM,IAAI,IAAI,QAAM,GAAG,IAAI,EAAE,KAAK,EAAE;AAE1C,MAAI,CAAC,CAAC,OAAO,MAAM,IAAI,EAAE,SAAS,GAAG,GAAG;AACtC,WAAO;AAAA,EACT;AAGA,QAAM,IAAI,IAAI,CAAC;AACf,QAAM,IAAI,IAAI,CAAC,EAAE,SAAS,MAAM,IAAI,CAAC,IAAI;AACzC,QAAM,IAAI,IAAI,IAAI,SAAS,CAAC,EAAE,SAAS,MAAM,IAAI,IAAI,SAAS,CAAC,IAAI;AAInE,MAAI,CAAC,UAAU,KAAK,CAAC,UAAU,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,GAAG,SAAS,CAAC,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;AAC1D,IAAE,QAAQ;AACV,QAAM,YAAY,MAAM,UAAU,OAAO,QAAM,CAAC,CAAC,KAAK,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;AAC5E,SAAO;AAEP,WAAS,WAAW;AAClB,WAAO,EAAE,MAAM,QAAQ,YAAY,EAAE;AAAA,EACvC;AAEA,WAAS,WAAW;AAClB,QAAI,CAAC,GAAG;AACN,aAAO;AAAA,IACT;AACA,QAAI,GAAG;AAEL,aAAO,GAAG,EAAE,KAAK,GAAG,IAAI,MAAM,EAAE;AAAA,IAClC;AACA,WAAO,EAAE;AAAA,EACX;AAEA,WAAS,WAAW;AAClB,WAAO,IAAI,EAAE,QAAQ;AAAA,EACvB;AAEA,WAAS,YAAY;AAEnB,QAAI,EAAE,MAAM,MAAM,mUAAmU,KACjV,EAAE,MAAM,MAAM,0UAA0U,GAAG;AAC7V,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAGA,WAAS,YAAY;AACnB,QAAI,CAAC,GAAG;AACN,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,MAAM,MAAM,6FAA6F;AAAA,EACpH;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/multiple-subfield-0.js"],
|
|
4
|
-
"sourcesContent": ["// import createDebugLogger from 'debug';\n//
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["// import createDebugLogger from 'debug';\n// const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:multiple-subfield-0');\n\nimport {fieldHasSubfield, fieldToString} from './utils.js';\n\nconst asteriNamePrefixes = ['(FI-ASTERI-N)', '(FIN11)', 'http://urn.fi/URN:NBN:fi:au:finaf:', 'https://urn.fi/URN:NBN:fi:au:finaf:'];\n\nexport default function () {\n\n return {\n description: 'If Asteri subfield $0 is found, remove non-Asteri $0 subfields',\n validate, fix\n };\n\n function fix(record) {\n function fixField(field) {\n const removableSubfields = fieldGetDeletableSubfields(field);\n removableSubfields.forEach(sf => record.removeSubfield(sf, field));\n }\n\n const res = {message: [], fix: [], valid: true};\n\n const relevantFields = getRelevantFields(record);\n\n relevantFields.forEach(field => fixField(field));\n\n // message.valid = !(message.message.length >= 1);\n return res;\n }\n\n function validate(record) {\n function validateField(field) {\n const relevantSubfields = fieldGetDeletableSubfields(field);\n if (relevantSubfields.length === 0) {\n return 'TROUBLE';\n }\n return `Field '${fieldToString(field)}' contains deletable $0 subfield(s): ${relevantSubfields.map(sf => sf.value).join(', ')}`;\n }\n const relevantFields = getRelevantFields(record);\n const messages = relevantFields.map(field => validateField(field));\n const res = {message: messages};\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function fieldGetSubfields(field, code) {\n return field.subfields.filter(sf => sf.code === code);\n }\n\n function isDeletableNamePartID(value) {\n // List here $0s that always refer to name part, and to never to title part\n if (value.match(/(?:isni|orcid)/ui)) {\n return true;\n }\n return false;\n }\n\n function isAsteriNameId(value) { // This is true if have a valid Asteri entry (nine digits etc)\n const nineDigitTail = value.slice(-9);\n if (!(/^[0-9]{9}$/u).test(nineDigitTail)) {\n return false;\n }\n // Normalize prefix:\n const currPrefix = value.slice(0, -9);\n\n if (asteriNamePrefixes.includes(currPrefix)) {\n return true;\n }\n return false;\n }\n\n function neverDropThisID(value) {\n if (isAsteriNameId(value)) {\n return true;\n }\n\n const prefixes = ['(FIN', '(FI-'];\n if (prefixes.some(prefix => value.startsWith(prefix))) {\n return true;\n }\n\n return false;\n }\n\n\n function fieldHasTitlePart(field) {\n if (['600', '610', '700', '710', '800', '810'].includes(field.tag)) {\n if (fieldHasSubfield(field, 't')) {\n return true;\n }\n }\n return false;\n }\n\n function fieldGetDeletableSubfields(field) {\n const subfield0s = fieldGetSubfields(field, '0');\n\n if (subfield0s.length < 2) {\n return []; // We have nothing to delete\n }\n\n // Field must contain non-Asteri subfields and Asteri subfiels.\n const nonAsteriNameSubfields = subfield0s.filter(sf => !isAsteriNameId(sf.value));\n if (nonAsteriNameSubfields.length === 0 || nonAsteriNameSubfields.length === subfield0s.length) {\n return [];\n }\n\n const suspiciousSubfields = nonAsteriNameSubfields.filter(sf => !neverDropThisID(sf.value));\n\n // Field has deletable name part $0s:\n const otherKnownNamePartIdentifiers = suspiciousSubfields.filter(sf => isDeletableNamePartID(sf.value));\n\n if (fieldHasTitlePart(field)) {\n return otherKnownNamePartIdentifiers;\n }\n\n return suspiciousSubfields;\n }\n\n function fieldIsRelevant(field) {\n const subfields = fieldGetDeletableSubfields(field);\n return subfields.length > 0;\n }\n\n function getRelevantFields(record) {\n return record.fields.filter(field => field.subfields && fieldIsRelevant(field));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/normalize-dashes.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import { fieldToString, isContentSubfieldCode, nvdebug } from "./utils.js";
|
|
3
2
|
import createDebugLogger from "debug";
|
|
4
3
|
const debug = createDebugLogger("@natlibfi/marc-record-validators-melinda:normaliza-dashes");
|
|
@@ -29,7 +28,7 @@ export default function() {
|
|
|
29
28
|
function validateField(field, res) {
|
|
30
29
|
const orig = fieldToString(field);
|
|
31
30
|
nvdebug(` VALIDATE FIELD '${orig}'`, debugDev);
|
|
32
|
-
const normalizedField = fixDashes(
|
|
31
|
+
const normalizedField = fixDashes(structuredClone(field));
|
|
33
32
|
const mod = fieldToString(normalizedField);
|
|
34
33
|
if (orig === mod) {
|
|
35
34
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/normalize-dashes.js"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["\nimport {fieldToString, isContentSubfieldCode, nvdebug} from './utils.js';\n\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:normaliza-dashes');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\n\n// Author(s): Nicholas Volk\nexport default function () {\n\n return {\n description: 'Normalize various dashes to \"-\"',\n validate, fix\n };\n\n function fix(record) {\n nvdebug(`FIX ME`, debugDev);\n record.fields.forEach(field => {\n fixDashes(field);\n });\n\n const res = {message: [], fix: [], valid: true};\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n nvdebug(`VALIDATE ME`, debugDev);\n record.fields?.forEach(field => {\n validateField(field, res);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res) {\n const orig = fieldToString(field);\n nvdebug(` VALIDATE FIELD '${orig}'`, debugDev);\n\n const normalizedField = fixDashes(structuredClone(field));\n const mod = fieldToString(normalizedField);\n if (orig === mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n return;\n }\n res.message.push(`'TODO: ${orig}' => '${mod}'`);\n return;\n }\n}\n\n\nfunction fixDashes(field) {\n if (!field.subfields) {\n return field;\n }\n\n nvdebug(`Dashing ${fieldToString(field)}`, debugDev);\n\n field.subfields.forEach(sf => subfieldFixDashes(sf));\n\n return field;\n\n function subfieldFixDashes(subfield) {\n if (!isContentSubfieldCode(subfield.code, field.tag)) {\n return;\n }\n // Normalize dashes U+2010 ... U+2015 to '-':\n subfield.value = subfield.value.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015]/ug, '-');\n }\n\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAQ,eAAe,uBAAuB,eAAc;AAE5D,OAAO,uBAAuB;AAE9B,MAAM,QAAQ,kBAAkB,2DAA2D;AAE3F,MAAM,WAAW,MAAM,OAAO,KAAK;AAInC,0BAA2B;AAEzB,SAAO;AAAA,IACL,aAAa;AAAA,IACb;AAAA,IAAU;AAAA,EACZ;AAEA,WAAS,IAAI,QAAQ;AACnB,YAAQ,UAAU,QAAQ;AAC1B,WAAO,OAAO,QAAQ,WAAS;AAC7B,gBAAU,KAAK;AAAA,IACjB,CAAC;AAED,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAC9C,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,YAAQ,eAAe,QAAQ;AAC/B,WAAO,QAAQ,QAAQ,WAAS;AAC9B,oBAAc,OAAO,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK;AACjC,UAAM,OAAO,cAAc,KAAK;AAChC,YAAQ,oBAAoB,IAAI,KAAK,QAAQ;AAE7C,UAAM,kBAAkB,UAAU,gBAAgB,KAAK,CAAC;AACxD,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB;AAAA,IACF;AACA,QAAI,QAAQ,KAAK,UAAU,IAAI,SAAS,GAAG,GAAG;AAC9C;AAAA,EACF;AACF;AAGA,SAAS,UAAU,OAAO;AACxB,MAAI,CAAC,MAAM,WAAW;AACpB,WAAO;AAAA,EACT;AAEA,UAAQ,WAAW,cAAc,KAAK,CAAC,IAAI,QAAQ;AAEnD,QAAM,UAAU,QAAQ,QAAM,kBAAkB,EAAE,CAAC;AAEnD,SAAO;AAEP,WAAS,kBAAkB,UAAU;AACnC,QAAI,CAAC,sBAAsB,SAAS,MAAM,MAAM,GAAG,GAAG;AACpD;AAAA,IACF;AAEA,aAAS,QAAQ,SAAS,MAAM,QAAQ,4CAA4C,GAAG;AAAA,EACzF;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import { fieldToString } from "./utils.js";
|
|
3
2
|
export default function() {
|
|
4
3
|
return {
|
|
@@ -25,7 +24,7 @@ export default function() {
|
|
|
25
24
|
if (!field.subfields) {
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
28
|
-
const normalizedField =
|
|
27
|
+
const normalizedField = structuredClone(field);
|
|
29
28
|
fieldNormalizeControlNumbers(normalizedField);
|
|
30
29
|
const orig = fieldToString(field);
|
|
31
30
|
const mod = fieldToString(normalizedField);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/normalize-identifiers.js"],
|
|
4
|
-
"sourcesContent": ["// Relocated from melinda-marc-record-merge-reducers (and renamed)\nimport
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["// Relocated from melinda-marc-record-merge-reducers (and renamed)\n\nimport {fieldToString, /*nvdebug*/} from './utils.js';\n\n//const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:normalize-identifiers');\n//const debugData = debug.extend('data');\n//const debugDev = debug.extend('dev');\n\nexport default function () {\n\n // NB! We should and could handle ISNIs here as well.\n return {\n description: 'Normalizes identifiers such as subfield $0 values',\n validate, fix\n };\n\n function fix(record) {\n const res = {message: [], fix: [], valid: true};\n //message.fix = [];\n\n // Actual parsing of all fields\n /*\n if (!record.fields) {\n return false;\n }\n */\n\n //nvdebug(`NORMALIZE CONTROL NUMBER FIX`, debugDev);\n record.fields.forEach(field => {\n //nvdebug(` NORMALIZE CONTROL NUMBER FIX ${fieldToString(field)}`, debugDev);\n\n fieldNormalizeControlNumbers(field);\n //validateField(field, true, message);\n });\n\n // message.valid = !(message.message.length >= 1);\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n //nvdebug(`NORMALIZE CONTROL NUMBER VALIDATE`, debugDev);\n // Actual parsing of all fields\n /*\n if (!record.fields) {\n return false;\n }\n */\n\n record.fields.forEach(field => {\n //nvdebug(` NORMALIZE CONTROL NUMBER VALIDATE ${fieldToString(field)}`, debugDev);\n validateField(field, res);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res) {\n if (!field.subfields) {\n return;\n }\n\n const normalizedField = structuredClone(field);\n fieldNormalizeControlNumbers(normalizedField);\n\n const orig = fieldToString(field);\n const mod = fieldToString(normalizedField);\n if (orig !== mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n res.message.push(`'${orig}' could do with control number identifier normalization`);\n return;\n }\n\n return;\n }\n}\n\nconst alephInternal01 = '(FIN01)';\nconst alephInternal10 = '(FIN10)';\nconst alephInternal11 = '(FIN11)';\nconst alephInternal12 = '(FIN12)';\nconst alephInternal13 = '(FIN13)';\n\nconst standard01 = '(FI-MELINDA)';\nconst standard10 = '(FI-ASTERI-S)';\nconst standard11 = '(FI-ASTERI-N)';\nconst standard12 = '(FI-ASTERI-A)';\nconst standard13 = '(FI-ASTERI-W)';\n\nconst both01 = {ALEPH_INTERNAL: alephInternal01, STANDARD: standard01};\nconst both10 = {ALEPH_INTERNAL: alephInternal10, STANDARD: standard10};\nconst both11 = {ALEPH_INTERNAL: alephInternal11, STANDARD: standard11};\nconst both12 = {ALEPH_INTERNAL: alephInternal12, STANDARD: standard12};\nconst both13 = {ALEPH_INTERNAL: alephInternal13, STANDARD: standard13};\n\nconst mappings = {\n 'FCC': both01,\n '(FI-ASTERI-A)': both12,\n '(FI-ASTERI-N)': both11,\n '(FI-ASTERI-S)': both10,\n '(FI-ASTERI-W)': both13,\n '(FI-MELINDA)': both01,\n '(FIN01)': both01,\n '(FIN10)': both10,\n '(FIN11)': both11,\n '(FIN12)': both12,\n '(FIN13)': both13,\n 'http://urn.fi/URN:NBN:fi:au:finaf:': both11,\n 'https://urn.fi/URN:NBN:fi:au:finaf:': both11\n};\n\nfunction normalizeNineDigitIDs(value, targetFormat = 'ALEPH_INTERNAL') {\n // $value should be prefix + nine-digits. Do nothing if nine-digit tail condition is not met:\n const nineDigitTail = value.slice(-9);\n if (!(/^[0-9]{9}$/u).test(nineDigitTail)) {\n return value;\n }\n // Normalize prefix:\n const currPrefix = value.slice(0, -9);\n\n if (currPrefix in mappings) {\n //nvdebug(`${currPrefix}, TF:${targetFormat}...`, debugDev);\n //nvdebug(`${JSON.stringify(mappings[currPrefix])}`, debugDev);\n return `${mappings[currPrefix][targetFormat]}${nineDigitTail}`;\n }\n return value;\n}\n\nexport function isIsni(value) {\n if ((/^\\(isni\\)(?: ?[0-9]{4}){4}$/u).test(value)) {\n return true;\n }\n if ((/^https:\\/\\/isni.org\\/isni\\/[0-9]{16}$/u).test(value)) {\n return true;\n }\n return false;\n}\n\nexport function normalizeIsni(value) {\n if (isIsni(value)) {\n return `https://isni.org/isni/${value.replace(/[^0-9]/gu, '')}`;\n }\n return value;\n}\n\n\nexport function normalizeControlSubfieldValue(value = '', targetFormat = 'ALEPH_INTERNAL') {\n if (isIsni(value)) {\n return normalizeIsni(value);\n }\n\n const normalizedValue = normalizeNineDigitIDs(value, targetFormat);\n if (normalizedValue !== value) {\n return normalizedValue;\n }\n\n // Something for isni IDs?\n return value;\n}\n\n//export function normalizableSubfieldPrefix(tag, sf) {\nexport function normalizeAs(tag, subfieldCode) {\n //nvdebug(`nAs ${tag}, ${subfieldCode}`, debugDev);\n if (subfieldCode === '0' || subfieldCode === '1' || subfieldCode === 'w') {\n return 'ALEPH_INTERNAL';\n }\n\n if (tag === '035' && ['a', 'z'].includes(subfieldCode)) {\n return 'STANDARD';\n }\n return undefined;\n}\n\nexport function fieldNormalizeControlNumbers(field) {\n // Rename \"Prefixes\" as \"ControlNumberIdentifiers\"?\n // No, since isni etc... however, just \"ControlNumber\" would do...\n // \"identifiers\" ?\n if (!field.subfields) {\n return;\n }\n\n field.subfields.forEach(sf => {\n const targetFormat = normalizeAs(field.tag, sf.code);\n if (targetFormat !== undefined) {\n //nvdebug(`NORMALIZE SUBFIELD $${sf.code} IN FIELD: '${fieldToString(field)}' TO ${targetFormat}`, debugDev);\n sf.value = normalizeControlSubfieldValue(sf.value, targetFormat);\n return;\n }\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import { fieldToString } from "./utils.js";
|
|
3
2
|
export default function() {
|
|
4
3
|
return {
|
|
@@ -24,7 +23,7 @@ export default function() {
|
|
|
24
23
|
}
|
|
25
24
|
function validateField(field, res) {
|
|
26
25
|
const orig = fieldToString(field);
|
|
27
|
-
const normalizedField =
|
|
26
|
+
const normalizedField = structuredClone(field);
|
|
28
27
|
normalizeQualifyingInformationField(normalizedField);
|
|
29
28
|
const mod = fieldToString(normalizedField);
|
|
30
29
|
if (orig !== mod) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/normalize-qualifying-information.js"],
|
|
4
|
-
"sourcesContent": ["//import createDebugLogger from 'debug';\nimport
|
|
5
|
-
"mappings": "AACA,
|
|
4
|
+
"sourcesContent": ["//import createDebugLogger from 'debug';\nimport {fieldToString} from './utils.js';\n\n// Author(s): Nicholas Volk\nexport default function () {\n\n return {\n // Fixes MELINDA-8740\n description: 'Normalize qualifying information (020$q, 015$q, 024$q, 028$q)',\n validate, fix\n };\n\n function fix(record) {\n record.fields.forEach(field => {\n normalizeQualifyingInformationField(field);\n });\n\n const res = {message: [], fix: [], valid: true};\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n record.fields?.forEach(field => {\n validateField(field, res);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res) {\n const orig = fieldToString(field);\n\n const normalizedField = structuredClone(field);\n normalizeQualifyingInformationField(normalizedField);\n const mod = fieldToString(normalizedField);\n if (orig !== mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n res.message.push(`'TODO: ${orig}' => '${mod}'`);\n return;\n }\n return;\n }\n}\n\n\nfunction normalizeQualifyingInformationField(field) {\n if (!['015', '020', '024', '028'].includes(field.tag) || !field.subfields) {\n return;\n }\n\n field.subfields.forEach(sf => fixSubfield(sf));\n return;\n\n function fixSubfield(sf) {\n if (sf.code !== 'q') {\n return;\n }\n sf.value = normalizeValue(sf.value);\n }\n\n function normalizeValue(val) {\n // Should we do English as well: \"coil bound\" and \"comb-bound\" => \"spiral-bound\" (as per MTS)?\n\n if (val.match(/^(?:hft|h\u00E4ftad)[.,]*$/iu)) { // MELINDA-8740\n return 'mjuka p\u00E4rmar';\n }\n\n if (val.match(/^inb(?:\\.|unden)[.,]*$/iu)) { // MELINDA-8740\n return 'h\u00E5rda p\u00E4rmar';\n }\n\n if (val === 'rengaskirja') { // https://www.kiwi.fi/display/melinda/Talonmies+tiedottaa+16.12.2021\n return 'kierreselk\u00E4';\n }\n\n if (val === 'ringp\u00E4rm') { // https://www.kiwi.fi/display/melinda/Talonmies+tiedottaa+16.12.2021\n return 'spiralrygg';\n }\n\n if (val.match(/^nid(?:\\.|ottu)[.,]*$/iu)) { // MELINDA-8740\n return 'pehme\u00E4kantinen';\n }\n\n if (val.match(/^sid(?:\\.|ottu)[.,]*$/iu)) { // MELINDA-8740\n return 'kovakantinen';\n }\n return val;\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAQ,qBAAoB;AAG5B,0BAA2B;AAEzB,SAAO;AAAA;AAAA,IAEL,aAAa;AAAA,IACb;AAAA,IAAU;AAAA,EACZ;AAEA,WAAS,IAAI,QAAQ;AACnB,WAAO,OAAO,QAAQ,WAAS;AAC7B,0CAAoC,KAAK;AAAA,IAC3C,CAAC;AAED,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAC9C,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,WAAO,QAAQ,QAAQ,WAAS;AAC9B,oBAAc,OAAO,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK;AACjC,UAAM,OAAO,cAAc,KAAK;AAEhC,UAAM,kBAAkB,gBAAgB,KAAK;AAC7C,wCAAoC,eAAe;AACnD,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB,UAAI,QAAQ,KAAK,UAAU,IAAI,SAAS,GAAG,GAAG;AAC9C;AAAA,IACF;AACA;AAAA,EACF;AACF;AAGA,SAAS,oCAAoC,OAAO;AAClD,MAAI,CAAC,CAAC,OAAO,OAAO,OAAO,KAAK,EAAE,SAAS,MAAM,GAAG,KAAK,CAAC,MAAM,WAAW;AACzE;AAAA,EACF;AAEA,QAAM,UAAU,QAAQ,QAAM,YAAY,EAAE,CAAC;AAC7C;AAEA,WAAS,YAAY,IAAI;AACvB,QAAI,GAAG,SAAS,KAAK;AACnB;AAAA,IACF;AACA,OAAG,QAAQ,eAAe,GAAG,KAAK;AAAA,EACpC;AAEA,WAAS,eAAe,KAAK;AAG3B,QAAI,IAAI,MAAM,yBAAyB,GAAG;AACxC,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,MAAM,0BAA0B,GAAG;AACzC,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ,eAAe;AACzB,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ,eAAY;AACtB,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,MAAM,yBAAyB,GAAG;AACxC,aAAO;AAAA,IACT;AAEA,QAAI,IAAI,MAAM,yBAAyB,GAAG;AACxC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import { convert as nongenericNormalization } from "./unicode-decomposition.js";
|
|
3
2
|
import { fieldToString } from "./utils.js";
|
|
4
3
|
export default function() {
|
|
@@ -27,7 +26,7 @@ export default function() {
|
|
|
27
26
|
return;
|
|
28
27
|
}
|
|
29
28
|
const orig = fieldToString(field);
|
|
30
|
-
const normalizedField = fieldFixComposition(
|
|
29
|
+
const normalizedField = fieldFixComposition(structuredClone(field));
|
|
31
30
|
const mod = fieldToString(normalizedField);
|
|
32
31
|
if (orig !== mod) {
|
|
33
32
|
res.message.push(`'${orig}' requires normalization`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/normalize-utf8-diacritics.js"],
|
|
4
|
-
"sourcesContent": ["//import createDebugLogger from 'debug';\nimport
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["//import createDebugLogger from 'debug';\n\nimport {convert as nongenericNormalization} from './unicode-decomposition.js';\nimport {fieldToString /*, nvdebug */} from './utils.js';\n\n// Note that https://github.com/NatLibFi/marc-record-validators-melinda/blob/master/src/unicode-decomposition.js contains\n// similar functionalities. It's less generic and lacks diacritic removal but has it advantages as well.\n\n//const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda/normalize-utf-diacritics');\n//const debugData = debug.extend('data');\n//const debugDev = debug.extend('dev');\n\n\n// See also https://github.com/NatLibFi/marc-record-validators-melinda/blob/master/src/unicode-decomposition.js .\n// It uses a list of convertable characters whilst this uses a generic stuff as well.\n// It handles various '.' and '\u00A9' type normalizations as well.\n// NB! This version has minor bug/feature issue regarding fixComposition()\n\n// Author(s): Nicholas Volk\nexport default function () {\n\n return {\n description: 'Generic normalization of latin UTF-8 diacritics. Precompose Finnish \u00E5, \u00E4 and \u00F6. Decompose others.',\n validate, fix\n };\n\n function fix(record) {\n const res = {message: [], fix: [], valid: true};\n //message.fix = [];\n\n // Actual parsing of all fields\n /*\n if (!record.fields) {\n return false;\n }\n */\n\n record.fields.forEach(field => {\n fieldFixComposition(field);\n //validateField(field, true, message);\n });\n\n // message.valid = !(message.message.length >= 1);\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n // Actual parsing of all fields\n /*\n if (!record.fields) {\n return false;\n }\n */\n\n record.fields.forEach(field => {\n validateField(field, res);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res) {\n if (!field.subfields) {\n return;\n }\n const orig = fieldToString(field);\n\n const normalizedField = fieldFixComposition(structuredClone(field));\n const mod = fieldToString(normalizedField);\n if (orig !== mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n res.message.push(`'${orig}' requires normalization`);\n return;\n }\n return;\n }\n}\n\n\n// Traditionally these six are precomposed and all the rest decomposed\nexport function precomposeFinnishLetters(value = '') {\n return value.\n replace(/a\u030A/gu, '\u00E5').\n replace(/a\u0308/gu, '\u00E4').\n replace(/o\u0308/gu, '\u00F6').\n replace(/A\u030A/gu, '\u00C5').\n replace(/A\u0308/gu, '\u00C4').\n replace(/O\u0308/gu, '\u00D6');\n}\n\nexport function fixComposition(value = '') {\n // Target: Diacritics use Melinda internal notation.\n // General solution: Decompose everything and then compose '\u00E5', '\u00E4', '\u00F6', '\u00C5', '\u00C4' and '\u00D6'.\n // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize\n // Bug/Feature: the generic normalize() function also normalizes non-latin encodings as well, is this ok?\n // Exception: Input contains non-Latin script letters: don't decompose (see field 880 tests):\n if (value.match(/[^\\p{Script=Latin}\\p{Script=Common}\\p{Script=Inherited}]/u)) {\n // Problem with this approach: mixed language content (eg. cyrillic + latin) won't get normalized.\n // Hack/Damage control: we might add decomposition rules for most common diacritis here (eg. \u00FC, \u00E9...).\n // OR we could split input to words and handle them separately?\n // NB! Hack not implemented yet. The main source of problematic case would probably be greek characters\n // within texts, that are written with latin alphabet.\n //return precomposeFinnishLetters(value);\n return nongenericNormalization(value);\n }\n return precomposeFinnishLetters(String(value).normalize('NFD'));\n}\n\n\nexport function fieldFixComposition(field) {\n if (!field.subfields) {\n return field;\n }\n //const originalValue = fieldToString(field);\n //nvdebug(`fFC: '${originalValue}'`, debugDev);\n field.subfields.forEach((subfield, index) => {\n field.subfields[index].value = fixComposition(subfield.value);\n });\n //const newValue = fieldToString(field);\n //if (originalValue !== newValue) {\n // nvdebug(`FIXCOMP: '${originalValue}' => '${newValue}'`, debugDev);\n //}\n return field;\n}\n\n/*\nexport function 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 sf.value = removeDecomposedDiacritics(sf.value);\n });\n\n function removeDecomposedDiacritics(value = '') {\n // NB #1: Does nothing to precomposed letters. String.normalize('NFD') can handle them.\n // NB #2: Finnish letters '\u00E5', '\u00E4', '\u00F6', '\u00C5', \u00C4', and '\u00D6' should be handled (=precomposed) before calling this.\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}\n*/\n\n"],
|
|
5
|
+
"mappings": "AAEA,SAAQ,WAAW,+BAA8B;AACjD,SAAQ,qBAAmC;AAgB3C,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;AAU9C,WAAO,OAAO,QAAQ,WAAS;AAC7B,0BAAoB,KAAK;AAAA,IAE3B,CAAC;AAGD,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AASxB,WAAO,OAAO,QAAQ,WAAS;AAC7B,oBAAc,OAAO,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK;AACjC,QAAI,CAAC,MAAM,WAAW;AACpB;AAAA,IACF;AACA,UAAM,OAAO,cAAc,KAAK;AAEhC,UAAM,kBAAkB,oBAAoB,gBAAgB,KAAK,CAAC;AAClE,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB,UAAI,QAAQ,KAAK,IAAI,IAAI,0BAA0B;AACnD;AAAA,IACF;AACA;AAAA,EACF;AACF;AAIO,gBAAS,yBAAyB,QAAQ,IAAI;AACnD,SAAO,MACL,QAAQ,QAAQ,MAAG,EACnB,QAAQ,QAAQ,MAAG,EACnB,QAAQ,QAAQ,MAAG,EACnB,QAAQ,QAAQ,MAAG,EACnB,QAAQ,QAAQ,MAAG,EACnB,QAAQ,QAAQ,MAAG;AACvB;AAEO,gBAAS,eAAe,QAAQ,IAAI;AAMzC,MAAI,MAAM,MAAM,2DAA2D,GAAG;AAO5E,WAAO,wBAAwB,KAAK;AAAA,EACtC;AACA,SAAO,yBAAyB,OAAO,KAAK,EAAE,UAAU,KAAK,CAAC;AAChE;AAGO,gBAAS,oBAAoB,OAAO;AACzC,MAAI,CAAC,MAAM,WAAW;AACpB,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,QAAQ,CAAC,UAAU,UAAU;AAC3C,UAAM,UAAU,KAAK,EAAE,QAAQ,eAAe,SAAS,KAAK;AAAA,EAC9D,CAAC;AAKD,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from "clone";
|
|
2
1
|
import { fieldStripPunctuation } from "./punctuation2.js";
|
|
3
2
|
import { fieldToString, isContentSubfieldCode } from "./utils.js";
|
|
4
3
|
import { fieldNormalizeControlNumbers } from "./normalize-identifiers.js";
|
|
@@ -187,7 +186,7 @@ function normalizeSubfieldValue(value, subfieldCode, tag) {
|
|
|
187
186
|
return value;
|
|
188
187
|
}
|
|
189
188
|
export function cloneAndRemovePunctuation(field) {
|
|
190
|
-
const clonedField =
|
|
189
|
+
const clonedField = structuredClone(field);
|
|
191
190
|
if (fieldSkipNormalization(field)) {
|
|
192
191
|
return clonedField;
|
|
193
192
|
}
|
|
@@ -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\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": "
|
|
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*/\n\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 = structuredClone(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 structuredClone(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
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import { MarcRecord } from "@natlibfi/marc-record";
|
|
2
1
|
import createDebug from "debug";
|
|
2
|
+
import { MarcRecord } from "@natlibfi/marc-record";
|
|
3
3
|
import { autRules, bibRules } from "./rules/index.js";
|
|
4
4
|
const debug = createDebug("@natlibfi/marc-record-validator-melinda/punctuation:dev");
|
|
5
|
-
function cloneDeep(field) {
|
|
6
|
-
const r = new MarcRecord();
|
|
7
|
-
r.appendField(field);
|
|
8
|
-
return r.get(field.tag)?.[0];
|
|
9
|
-
}
|
|
10
5
|
export default function() {
|
|
11
6
|
function readPunctuationRulesFromJSON(recordType) {
|
|
12
7
|
const json = getRules(recordType);
|
|
@@ -40,7 +35,7 @@ export default function() {
|
|
|
40
35
|
}
|
|
41
36
|
function validateField(recordType = "a") {
|
|
42
37
|
return function(element) {
|
|
43
|
-
const testField =
|
|
38
|
+
const testField = structuredClone(element);
|
|
44
39
|
debug(`Original field: ${JSON.stringify(element)}`);
|
|
45
40
|
const punctuated = punctuateField(testField, recordType);
|
|
46
41
|
debug(`Punctuation result: ${JSON.stringify(punctuated)}`);
|
|
@@ -48,7 +43,7 @@ export default function() {
|
|
|
48
43
|
debug("No punctuation result -> true");
|
|
49
44
|
return true;
|
|
50
45
|
}
|
|
51
|
-
if (
|
|
46
|
+
if (JSON.stringify(punctuated) === JSON.stringify(element)) {
|
|
52
47
|
debug(`Original field (element): ${JSON.stringify(element)}`);
|
|
53
48
|
debug("Punctuation result equals original field");
|
|
54
49
|
return true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/punctuation/index.js"],
|
|
4
|
-
"sourcesContent": ["import {MarcRecord} from '@natlibfi/marc-record';\nimport
|
|
5
|
-
"mappings": "AAAA,
|
|
4
|
+
"sourcesContent": ["import createDebug from 'debug';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport {autRules, bibRules} from './rules/index.js';\n\n\nconst debug = createDebug('@natlibfi/marc-record-validator-melinda/punctuation:dev');\n\nexport default function () {\n function readPunctuationRulesFromJSON(recordType) {\n const json = getRules(recordType);\n const rules = json.filter(row => row.selector !== '').map(row => {\n const {selector, namePortion, description, portion, preceedingPunctuation, exceptions} = row;\n return {\n selector: new RegExp(selector.replace(/X/ug, '.'), 'u'),\n namePortion: namePortion.replace(/\\$/ug, '').trim(),\n description, portion, preceedingPunctuation, exceptions\n };\n });\n\n return rules;\n }\n\n function getRules(recordType) {\n if (recordType === 'z') {\n return autRules;\n }\n\n return bibRules;\n }\n\n function fieldToString(field) {\n if (field && field.subfields) {\n const ind1 = field.ind1 || ' ';\n const ind2 = field.ind2 || ' ';\n const subfields = field.subfields.map(subfield => `\u2021${subfield.code}${subfield.value}`).join('');\n return `${field.tag} ${ind1}${ind2} ${subfields}`;\n }\n\n return `${field.tag} ${field.value}`;\n }\n\n function validateField(recordType = 'a') {\n return function (element) {\n const testField = structuredClone(element);\n debug(`Original field: ${JSON.stringify(element)}`);\n const punctuated = punctuateField(testField, recordType);\n debug(`Punctuation result: ${JSON.stringify(punctuated)}`);\n if (!punctuated) {\n debug('No punctuation result -> true');\n return true;\n }\n\n if (JSON.stringify(punctuated) === JSON.stringify(element)) {\n debug(`Original field (element): ${JSON.stringify(element)}`);\n debug('Punctuation result equals original field');\n return true;\n }\n\n return false;\n };\n }\n\n function punctuateField(field, recordType) {\n const rules = readPunctuationRulesFromJSON(recordType);\n debug(`Handling field ${field.tag}`);\n debug(`Field contents: ${fieldToString(field)}`);\n const rulesForField = getRulesForField(field.tag, rules);\n if (rulesForField.length === 0) {\n debug(`No matching rules for field ${field.tag}`);\n return;\n }\n\n let currentPortion;\n let preceedingField;\n let inNamePortion = true;\n\n debug(`Field subfields: ${field.subfields.map(sub => sub.code)}`);\n debug(`Field portions: ${field.subfields.map(sub => getPortion(sub, rulesForField))}`);\n\n field.subfields.forEach(subfield => {\n debug(`Handling subfield ${subfield.code}`);\n let portion = getPortion(subfield, rulesForField);\n\n if (portion === false) {\n return;\n }\n\n if (portion === 'CF' || portion === 'NC') {\n return;\n }\n\n if (inNamePortion && portion.includes('T', 'S')) {\n debug(`Portion changed to ${portion}. Not in name portion anymore`);\n inNamePortion = false;\n }\n\n if (inNamePortion && portion === 'NT') {\n portion = 'N';\n }\n\n if (!inNamePortion && portion === 'NT') {\n portion = 'T';\n }\n\n debug(`Current portion is ${portion}.`);\n\n if (currentPortion) {\n if (currentPortion === portion) {\n debug(`Current stayed as ${portion}. Adding punctuation for subfield.`);\n addSubfieldPunctuation(preceedingField, subfield, rulesForField);\n } else {\n debug(`Current portion changed to ${portion}.`);\n if (portion !== 'S') {\n debug('Adding punctuation for portion.');\n addNamePortionPunctuation(preceedingField);\n }\n }\n }\n\n currentPortion = portion;\n preceedingField = subfield;\n });\n\n if (recordType !== 'z') {\n addNamePortionPunctuation(preceedingField);\n }\n\n debug(`After punctuation: ${fieldToString(field)}`);\n\n return field;\n }\n\n function getRulesForField(tag, rules) {\n return rules.filter(rule => rule.selector.test(tag));\n }\n\n function getPortion(subfield, rules) {\n debug(`Looking for namePortion for ${subfield.code}`);\n const [portion] = rules.filter(rule => rule.namePortion === subfield.code).map(rule => rule.portion);\n\n if (portion === undefined) {\n return false;\n }\n\n return portion.toUpperCase();\n }\n\n function addNamePortionPunctuation(preceedingSubfield) {\n const subfieldContainsPunctuation = (/[?\")\\].\\-!,]$/u).test(preceedingSubfield.value);\n if (!subfieldContainsPunctuation) {\n const nextValue = `${preceedingSubfield.value}.`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n }\n\n function addSubfieldPunctuation(preceedingSubfield, currentSubfield, rules) {\n const punctType = getPrecedingPunctuation(currentSubfield, rules);\n const exceptionsFunctions = getExceptions(currentSubfield, rules);\n\n const isExceptionCase = exceptionsFunctions.some(fn => fn(preceedingSubfield));\n\n if (isExceptionCase) {\n return;\n }\n\n const endsInPunctuation = (/[?\")\\]\\-!,]$/u).test(preceedingSubfield.value);\n debug(`addSubfieldPunctuation -- punctType: ${punctType} endsInPunctuation: ${endsInPunctuation}`);\n\n if (!endsInPunctuation) {\n if (punctType === 'PERIOD' && !(/\\.$/u).test(preceedingSubfield.value)) {\n const nextValue = `${preceedingSubfield.value}.`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n }\n\n if (punctType === 'COMMA') {\n if (!(/,$/u).test(preceedingSubfield.value)) {\n if (!(/^[[(]/u).test(currentSubfield.value)) {\n const nextValue = `${preceedingSubfield.value},`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n }\n }\n\n if (punctType === 'COND_COMMA') {\n if (!(/[-,]$/u).test(preceedingSubfield.value)) {\n const nextValue = `${preceedingSubfield.value},`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n }\n\n if (punctType === 'SPACECOLON') {\n if (!(/:$/u).test(preceedingSubfield.value)) {\n const nextValue = `${preceedingSubfield.value} :`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n if ((/[^ ]:$/u).test(preceedingSubfield.value)) {\n const nextValue = `${preceedingSubfield.value.slice(0, -1)} :`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n\n }\n\n debug('addSubfieldPunctuation -- end');\n }\n\n function getPrecedingPunctuation(subfield, rules) {\n const [punct] = rules.filter(rule => rule.namePortion === subfield.code).map(rule => rule.preceedingPunctuation);\n\n if (punct === undefined) {\n throw new Error(`Unknown subfield code ${subfield.code}`);\n }\n\n return punct.toUpperCase();\n }\n\n function getExceptions(subfield, rules) {\n const [exception] = rules.filter(rule => rule.namePortion === subfield.code).map(rule => parseExceptions(rule.exceptions));\n\n if (exception === undefined) {\n throw new Error(`Unknown subfield code ${subfield.code}`);\n }\n\n return exception;\n }\n\n function parseExceptions(expectionsString) {\n const exceptionRules = expectionsString.split('\\n');\n const exceptionFuncs = [];\n\n exceptionRules.forEach(exceptionRule => {\n const match = (/- (.*) if preceded by (.*)/u).exec(exceptionRule);\n if (match) {\n const [, type, preceededCode] = match;\n const normalizedType = type.trim().toUpperCase().trim();\n const normalizedCode = preceededCode.replace(/\\$/ug, '').trim();\n exceptionFuncs.push(ifPrecededByException(normalizedCode, normalizedType));\n }\n });\n\n return exceptionFuncs;\n }\n\n function ifPrecededByException(code, type) {\n return preceedingSubfield => {\n if (code === preceedingSubfield.code) {\n debug(`Adding ${type} to ${preceedingSubfield.code}`);\n if (type === 'SEMICOLON' && !(/;$/u).test(preceedingSubfield.value)) {\n const nextValue = `${preceedingSubfield.value} ;`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n\n if (type === 'COLON' && !(/:$/u).test(preceedingSubfield.value)) {\n const nextValue = `${preceedingSubfield.value} :`;\n debug(`Updated subfield ${preceedingSubfield.code} from '${preceedingSubfield.value}' to '${nextValue}'`);\n preceedingSubfield.value = nextValue;\n }\n\n return true;\n }\n\n return false;\n };\n }\n\n function validate(record) {\n return {valid: record.fields.every(validateField(record.leader[6]))};\n }\n\n function fix(record) {\n record.fields.map(field => punctuateField(field, record.leader[6]));\n return true;\n }\n\n return {\n description: 'Fixes punctuation of fields',\n validate,\n fix\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/punctuation2.js
CHANGED
|
@@ -3,7 +3,6 @@ import { tagToDataProvenanceSubfieldCode } from "./dataProvenanceUtils.js";
|
|
|
3
3
|
import { fieldGetUnambiguousTag } from "./subfield6Utils.js";
|
|
4
4
|
import createDebugLogger from "debug";
|
|
5
5
|
import { fieldToString, isContentSubfieldCode, nvdebug } from "./utils.js";
|
|
6
|
-
import clone from "clone";
|
|
7
6
|
const debug = createDebugLogger("@natlibfi/marc-record-validators-melinda:punctuation2");
|
|
8
7
|
const debugDev = debug.extend("dev");
|
|
9
8
|
const descriptionString = "Remove invalid and add valid punctuation to data fields";
|
|
@@ -41,7 +40,7 @@ function getNextRelevantSubfield(field, currSubfieldIndex) {
|
|
|
41
40
|
return field.subfields.find((subfield, index) => index > currSubfieldIndex && !isIrrelevantSubfield(subfield, field.tag));
|
|
42
41
|
}
|
|
43
42
|
export function fieldGetFixedString(field, add = true) {
|
|
44
|
-
const cloneField =
|
|
43
|
+
const cloneField = structuredClone(field);
|
|
45
44
|
const operation = add ? subfieldFixPunctuation : subfieldStripPunctuation;
|
|
46
45
|
cloneField.subfields.forEach((sf, i) => {
|
|
47
46
|
operation(cloneField, sf, getNextRelevantSubfield(cloneField, i));
|