diff --git a/package-lock.json b/package-lock.json index 85dfbd98..ef2593dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -448,6 +448,93 @@ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + } } }, "@istanbuljs/schema": { @@ -3100,40 +3187,6 @@ "strip-ansi": "^7.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -3143,23 +3196,6 @@ "ansi-regex": "^6.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - } - } - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -3632,60 +3668,6 @@ "strip-ansi": "^7.0.1" } }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/src/stores/config.js b/src/stores/config.js index ab6f3145..45afd0d6 100644 --- a/src/stores/config.js +++ b/src/stores/config.js @@ -61,7 +61,6 @@ export const useConfigStore = defineStore('config', { // profiles: 'https://editor.id.loc.gov/marva/util/profiles/profile/prod', profiles : 'https://editor.id.loc.gov/marva/dancer/api/serve/marva-prod/profile', - // profiles : 'https://editor.id.loc.gov/marva/dancer/api/serve/marva-prod-copy-subjects/profile', starting : 'https://editor.id.loc.gov/marva/dancer/api/serve/marva-prod/starting-points', // profiles: 'https://editor.id.loc.gov/marva/dancer/api/serve/marva-stage/profile', // starting : 'https://editor.id.loc.gov/marva/dancer/api/serve/marva-stage/starting-points', diff --git a/src/stores/profile.js b/src/stores/profile.js index 312d7676..f5c73ad2 100644 --- a/src/stores/profile.js +++ b/src/stores/profile.js @@ -19,21 +19,21 @@ import defaultComponents from "@/lib/defaults/default_components.json" import utilsProfile from '../lib/utils_profile' -import {unescape} from 'html-escaper'; +import { unescape } from 'html-escaper'; import short from 'short-uuid' const translator = short(); const decimalTranslator = short("0123456789"); -const hashCode = s => s.split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0) +const hashCode = s => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0) const LABEL_PREDICATES = [ - 'http://www.w3.org/2000/01/rdf-schema#label', - 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel', - 'http://id.loc.gov/ontologies/bibframe/code', - 'http://id.loc.gov/ontologies/bibframe/mainTitle', - 'http://id.loc.gov/ontologies/bibframe/title' + 'http://www.w3.org/2000/01/rdf-schema#label', + 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel', + 'http://id.loc.gov/ontologies/bibframe/code', + 'http://id.loc.gov/ontologies/bibframe/mainTitle', + 'http://id.loc.gov/ontologies/bibframe/title' ] let cachePt = {} @@ -46,8730 +46,8725 @@ const latinRegex = /^[A-z\s'\.,-\/#!$%\^&\*;:{}=\-_`~()0-9\u0000-\u007F\u0080-\u //https://stackoverflow.com/questions/49562546/how-to-get-all-properties-values-of-a-javascript-nested-objects-without-knowing // clean cacheGuid of items that match the children of the PT that is insert default values too -function cleanCacheGuid(cache, obj, target){ - return Object.keys(obj).map( - function(key){ - let value = obj[key] - if (key == "@guid" && value == target){ - delete cache[value] - return value - } else if(Array.isArray(value)) { - for (let el in value){ - return cleanCacheGuid( cache, value[el], target ) +function cleanCacheGuid(cache, obj, target) { + return Object.keys(obj).map( + function (key) { + let value = obj[key] + if (key == "@guid" && value == target) { + delete cache[value] + return value + } else if (Array.isArray(value)) { + for (let el in value) { + return cleanCacheGuid(cache, value[el], target) + } + } else if (typeof value === "object") { + return cleanCacheGuid(cache, value, target) + } } - }else if (typeof value === "object"){ - return cleanCacheGuid( cache, value, target ) - } - } - ) + ) } export const useProfileStore = defineStore('profile', { - state: () => ({ + state: () => ({ - // flag if the profiles have been loaded and processed - profilesLoaded: false, + // flag if the profiles have been loaded and processed + profilesLoaded: false, - // holds all profiles - profiles: {}, + // holds all profiles + profiles: {}, - // holds all rts, with its ID as the key - rtLookup: {}, + // holds all rts, with its ID as the key + rtLookup: {}, - // the starting points that display on the create new page - startingPoints: {}, - copyCatMode: false, - copyCatSearch: null, + // the starting points that display on the create new page + startingPoints: {}, + copyCatMode: false, + copyCatSearch: null, - // the current active profile - activeProfile: {}, + // the current active profile + activeProfile: {}, - activeProfileSaved: false, - activeProfilePosted: false, - activeProfilePostedTimestamp: false, + activeProfileSaved: false, + activeProfilePosted: false, + activeProfilePostedTimestamp: false, - showPostModal: false, - showRecoveryModal: false, - showValidateModal: false, - showHubStubCreateModal: false, - showNacoStubCreateModal: false, - lastComplexLookupString: "", // used in the naco stub process to paste what you were working on in the complex lookup + showPostModal: false, + showRecoveryModal: false, + showValidateModal: false, + showHubStubCreateModal: false, + showNacoStubCreateModal: false, + lastComplexLookupString: "", // used in the naco stub process to paste what you were working on in the complex lookup - showItemInstanceSelection: false, - activeHubStubData:{ - }, - activeHubStubComponent:{}, + showItemInstanceSelection: false, + activeHubStubData: { + }, + activeHubStubComponent: {}, - activeNARStubComponent:{}, + activeNARStubComponent: {}, - savedNARModalData:{}, - savedHubModalData:{}, + savedNARModalData: {}, + savedHubModalData: {}, - showMarvaLogModal: false, - marvaLogResults: [], - marvaLogSearchValue: '', - marvaLogLoading: false, + showMarvaLogModal: false, + marvaLogResults: [], + marvaLogSearchValue: '', + marvaLogLoading: false, - showUserDirectoryModal: false, - userDirectoryResults: [], - userDirectoryLoading: false, + showUserDirectoryModal: false, + userDirectoryResults: [], + userDirectoryLoading: false, - showFolioSyncModal: false, + showFolioSyncModal: false, - showShelfListingModal: false, - activeShelfListData:{ - class:null, - cutter:null, - classGuid:null, - cutterGuid: null, - componentGuid: null, - componentPropertyPath:null - }, - showAutoDeweyModal: false, - showAdHocModal: false, - deweyData: { - lcc: null, - guid: null, - structure: null, - }, + showShelfListingModal: false, + activeShelfListData: { + class: null, + cutter: null, + classGuid: null, + cutterGuid: null, + componentGuid: null, + componentPropertyPath: null + }, + showAutoDeweyModal: false, + showAdHocModal: false, + deweyData: { + lcc: null, + guid: null, + structure: null, + }, - showYoshinoSubjectsModal: false, - yoshinoResults: null, - yoshinoInsertedSubjects: [], + showYoshinoSubjectsModal: false, + yoshinoResults: null, + yoshinoInsertedSubjects: [], - cammModeErrors: { + cammModeErrors: { - }, + }, - linkedData: {}, + linkedData: {}, - componentLibrary : { - profiles:{ + componentLibrary: { + profiles: { - } - }, + } + }, - mostCommonNonLatinScript: null, - nonLatinScriptAgents: {}, + mostCommonNonLatinScript: null, + nonLatinScriptAgents: {}, - pairedLitearlIndicatorLookup: {}, + pairedLitearlIndicatorLookup: {}, - // bf:title component/predicate for example, value will be the structure object for this component + // bf:title component/predicate for example, value will be the structure object for this component - activeComponent: null, + activeComponent: null, - // bf:mainTitle for example, value will be the the structure object for this field - // main thing we can use it for is to see which field is currently active in the interface via the @guid - activeField: { '@guid' : null }, + // bf:mainTitle for example, value will be the the structure object for this field + // main thing we can use it for is to see which field is currently active in the interface via the @guid + activeField: { '@guid': null }, - dataChangedTimestamp: Date.now(), + dataChangedTimestamp: Date.now(), - // the active guid of the literal field being assinged a @lang value - // if it === false it hides the modal - literalLangInfo: null, - literalLangShow: false, + // the active guid of the literal field being assinged a @lang value + // if it === false it hides the modal + literalLangInfo: null, + literalLangShow: false, - // List of empty components for ad hoc mode - emptyComponents: {}, - hiddenSubjects: false, - hiddenClassNumbers: false, + // List of empty components for ad hoc mode + emptyComponents: {}, + hiddenSubjects: false, + hiddenClassNumbers: false, - localMarva: false, + localMarva: false, - // undo - currentState: null, - undoRecords: [], - redoRecords: [], - undoRedoLimit: 10 - }), - getters: { + // undo + currentState: null, + undoRecords: [], + redoRecords: [], + undoRedoLimit: 10 + }), + getters: { - /** - * Can be used to return the structure of the component by passing the GUID - * It doesn't care what profile it is in it will loop through all of them to find the unique GUID - * @param {string} guid - the guid of the component - * @return {object} - */ - returnStructureByGUID: (state) => { - return (guid) => { - for (let rt in state.activeProfile.rt){ - for (let pt in state.activeProfile.rt[rt].pt){ - if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid){ - return state.activeProfile.rt[rt].pt[pt] + /** + * Can be used to return the structure of the component by passing the GUID + * It doesn't care what profile it is in it will loop through all of them to find the unique GUID + * @param {string} guid - the guid of the component + * @return {object} + */ + returnStructureByGUID: (state) => { + return (guid) => { + for (let rt in state.activeProfile.rt) { + for (let pt in state.activeProfile.rt[rt].pt) { + if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid) { + return state.activeProfile.rt[rt].pt[pt] + } + } + } } - } - } - } - }, + }, - /** - * Can be used to return the rt of the component by passing the GUID - * It doesn't care what profile it is in it will loop through all of them to find the unique GUID - * @param {string} guid - the guid of the component - * @return {object} - */ - returnRtByGUID: (state) => { - return (guid) => { - for (let rt in state.activeProfile.rt){ - for (let pt in state.activeProfile.rt[rt].pt){ - if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid){ - return rt - } - } - } - } - }, + /** + * Can be used to return the rt of the component by passing the GUID + * It doesn't care what profile it is in it will loop through all of them to find the unique GUID + * @param {string} guid - the guid of the component + * @return {object} + */ + returnRtByGUID: (state) => { + return (guid) => { + for (let rt in state.activeProfile.rt) { + for (let pt in state.activeProfile.rt[rt].pt) { + if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid) { + return rt + } + } + } + } + }, - /** - * Can be used to return the structure of the component by passing the GUID - * It doesn't care what profile it is in it will loop through all of them to find the unique GUID - * @param {string} guid - the guid of the component - * @return {object} - */ - returnPreferenceIdByGUID: (state) => { - return (guid) => { - for (let rt in state.activeProfile.rt){ - for (let pt in state.activeProfile.rt[rt].pt){ - if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid){ - return state.activeProfile.rt[rt].pt[pt].preferenceId - } - } - } - } - }, + /** + * Can be used to return the structure of the component by passing the GUID + * It doesn't care what profile it is in it will loop through all of them to find the unique GUID + * @param {string} guid - the guid of the component + * @return {object} + */ + returnPreferenceIdByGUID: (state) => { + return (guid) => { + for (let rt in state.activeProfile.rt) { + for (let pt in state.activeProfile.rt[rt].pt) { + if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid) { + return state.activeProfile.rt[rt].pt[pt].preferenceId + } + } + } + } + }, - returnUserModifiedIdByGUID: (state) => { - return (guid) => { - for (let rt in state.activeProfile.rt){ - for (let pt in state.activeProfile.rt[rt].pt){ - if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid){ - return state.activeProfile.rt[rt].pt[pt].userModified + returnUserModifiedIdByGUID: (state) => { + return (guid) => { + for (let rt in state.activeProfile.rt) { + for (let pt in state.activeProfile.rt[rt].pt) { + if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid) { + return state.activeProfile.rt[rt].pt[pt].userModified + } + } + } } - } - } - } - }, + }, - returnComponentByPropertyLabel: (state) => { - return (label, profile) => { - for (let rt in state.activeProfile.rt){ - for (let pt in state.activeProfile.rt[rt].pt){ - if (state.activeProfile.rt[rt].pt[pt]['propertyLabel'].toLowerCase() === label.toLowerCase()){ - if (profile && rt.includes(profile)){ - return state.activeProfile.rt[rt].pt[pt] - } else if (!profile){ - return state.activeProfile.rt[rt].pt[pt] - } - } - } - } - } - }, + returnComponentByPropertyLabel: (state) => { + return (label, profile) => { + for (let rt in state.activeProfile.rt) { + for (let pt in state.activeProfile.rt[rt].pt) { + if (state.activeProfile.rt[rt].pt[pt]['propertyLabel'].toLowerCase() === label.toLowerCase()) { + if (profile && rt.includes(profile)) { + return state.activeProfile.rt[rt].pt[pt] + } else if (!profile) { + return state.activeProfile.rt[rt].pt[pt] + } + } + } + } + } + }, + + + /** Groups the library components into a array ready to render + * + * @return {array} + */ + returnComponentLibrary: (state) => { + // limit to the current profiles being used + // console.log(state.activeProfile) + // console.log(state.componentLibrary) + // return () => { + // return [state.componentLibrary] + // } + let results = [] + for (let key in state.activeProfile.rt) { + // Items have something added to the end of the key + if (key && key.includes(":Item")) { + if (key.includes("-") || key.includes("_")) { + let idx + idx = key.indexOf("_") + if (idx < 0) { + idx = key.indexOf("-") + } + key = key.slice(0, idx) + } + } + // ther are components saved for this profile + if (state.componentLibrary.profiles[key]) { + let groups = {} + let groupsOrder = [] + // loop through all the components sorted by position order + for (let group of state.componentLibrary.profiles[key].groups.sort(({ position: a }, { position: b }) => a - b)) { + + if (group.groupId === null) { + groups[group.id] = [group] + groupsOrder.push(group.id) + } else { + if (!groups[group.groupId]) { groups[group.groupId] = [] } + groups[group.groupId].push(group) + if (groupsOrder.indexOf(group.groupId) == -1) { + groupsOrder.push(group.groupId) + } + } + } + results.push({ groups: groups, groupsOrder: groupsOrder, profileId: key, label: key.split(":").slice(-1)[0] }) + } - /** Groups the library components into a array ready to render - * - * @return {array} - */ - returnComponentLibrary: (state) => { - // limit to the current profiles being used - // console.log(state.activeProfile) - // console.log(state.componentLibrary) - // return () => { - // return [state.componentLibrary] - - // } - let results = [] - for (let key in state.activeProfile.rt){ - // Items have something added to the end of the key - if (key && key.includes(":Item")){ - if (key.includes("-") || key.includes("_")){ - let idx - idx = key.indexOf("_") - if (idx < 0){ - idx = key.indexOf("-") - } - key = key.slice(0, idx) + if (usePreferenceStore().returnValue('--b-edit-main-splitpane-properties-show-defaults')) { + let groups = {} + let groupsOrder = [] + for (let dKey in defaultComponents.DefaultComponentLibrary.profiles) { + if (dKey.includes(key)) { + for (let group of defaultComponents.DefaultComponentLibrary.profiles[dKey].groups.sort(({ position: a }, { position: b }) => a - b)) { + if (group.groupId === null) { + groups[group.id] = [group] + groupsOrder.push(group.id) + } else { + if (!groups[group.groupId]) { groups[group.groupId] = [] } + groups[group.groupId].push(group) + if (groupsOrder.indexOf(group.groupId) == -1) { + groupsOrder.push(group.groupId) + } + } + } + results.push({ type: "default", groups: groups, groupsOrder: groupsOrder, profileId: dKey, label: key.split(":").slice(-1)[0] }) + } + } + } } - } - // ther are components saved for this profile - if (state.componentLibrary.profiles[key]){ - let groups = {} - let groupsOrder = [] - // loop through all the components sorted by position order - for (let group of state.componentLibrary.profiles[key].groups.sort(({position:a}, {position:b}) => a-b)){ - - if (group.groupId === null){ - groups[group.id] = [group] - groupsOrder.push(group.id) - }else{ - if (!groups[group.groupId]){groups[group.groupId]=[]} - groups[group.groupId].push(group) - if (groupsOrder.indexOf(group.groupId)==-1){ - groupsOrder.push(group.groupId) - } - } - } - results.push({groups:groups, groupsOrder:groupsOrder, profileId: key, label: key.split(":").slice(-1)[0]}) - } - if (usePreferenceStore().returnValue('--b-edit-main-splitpane-properties-show-defaults')){ - let groups = {} - let groupsOrder = [] - for (let dKey in defaultComponents.DefaultComponentLibrary.profiles){ - if (dKey.includes(key)){ - for (let group of defaultComponents.DefaultComponentLibrary.profiles[dKey].groups.sort(({position:a}, {position:b}) => a-b)){ - if (group.groupId === null){ - groups[group.id] = [group] - groupsOrder.push(group.id) - }else{ - if (!groups[group.groupId]){groups[group.groupId]=[]} - groups[group.groupId].push(group) - if (groupsOrder.indexOf(group.groupId)==-1){ - groupsOrder.push(group.groupId) - } - } - } - results.push({type: "default", groups:groups, groupsOrder:groupsOrder, profileId: dKey, label: key.split(":").slice(-1)[0]}) - } - } - } - } + // now go through and see if there are the the same group being used in multiple profiles if so + // that means they have cross profile components (2 fields in Work 1 in instance for exmaple) - // now go through and see if there are the the same group being used in multiple profiles if so - // that means they have cross profile components (2 fields in Work 1 in instance for exmaple) + let groupsCount = {} + let groupsCountDefault = {} + for (let profileComponents of results) { - let groupsCount = {} - let groupsCountDefault = {} - for (let profileComponents of results){ + let type = profileComponents.type - let type = profileComponents.type + for (let groupKey in profileComponents.groups) { + if (profileComponents.groups[groupKey].groupId !== null) { + for (let groupItem of profileComponents.groups[groupKey]) { + let group = groupsCount + if (type == 'default') { + group = groupsCountDefault + } + if (groupItem.groupId !== null) { + if (!group[groupItem.groupId]) { + group[groupItem.groupId] = [] + } + if (group[groupItem.groupId].indexOf(groupItem.structure.parentId) == -1) { + group[groupItem.groupId].push(groupItem.structure.parentId) + } + } + } + } + } + } - for (let groupKey in profileComponents.groups){ - if (profileComponents.groups[groupKey].groupId !== null){ - for (let groupItem of profileComponents.groups[groupKey]){ - let group = groupsCount - if (type == 'default'){ - group = groupsCountDefault - } - if (groupItem.groupId !== null){ - if (!group[groupItem.groupId]){ - group[groupItem.groupId]=[] + let groupsToMerge = [] + let groupsToMergeDefault = [] + for (let groupKey in groupsCount) { + if (groupsCount[groupKey].length > 1) { + groupsToMerge.push(groupKey) } - if (group[groupItem.groupId].indexOf(groupItem.structure.parentId)==-1){ - group[groupItem.groupId].push(groupItem.structure.parentId) + } + for (let groupKey in groupsCountDefault) { + if (groupsCountDefault[groupKey].length > 1) { + groupsToMergeDefault.push(groupKey) } - } } - } - } - } - let groupsToMerge = [] - let groupsToMergeDefault = [] - for (let groupKey in groupsCount){ - if (groupsCount[groupKey].length>1){ - groupsToMerge.push(groupKey) - } - } - for (let groupKey in groupsCountDefault){ - if (groupsCountDefault[groupKey].length>1){ - groupsToMergeDefault.push(groupKey) - } - } - - let mergeComponents = function (results, groupsToMerge, title){ - if (groupsToMerge.length>0){ - // we have to MERGE - let multiProfile = { - groups: {}, - groupsOrder: [], - label: title, - profileId: title, - type: title.includes("Default") ? 'default' : null - } - - for (let groupName of groupsToMerge){ - let tmpGroupComponents = [] - // remove them from the orginal group/profile and them to the multi profile - for (let profileComponents of results){ - if (profileComponents.groups[groupName]){ - tmpGroupComponents=tmpGroupComponents.concat( JSON.parse(JSON.stringify(profileComponents.groups[groupName])) ) - delete profileComponents.groups[groupName] - } - profileComponents.groupsOrder = profileComponents.groupsOrder.filter((v) => {return (v !== groupName)}) - } - - // put them into the multi profile - multiProfile.groups[groupName] = tmpGroupComponents - multiProfile.groupsOrder.push(groupName) - - // add a label to denote if the individual component is a work or instance whatever component. - for (let groupKey in multiProfile.groups){ - for (let component of multiProfile.groups[groupKey]){ - if (component.label.indexOf("(i)")>-1){ continue} - if (component.label.indexOf("(w)")>-1){ continue} - let initial = component.structure.parentId.split(':').slice(-1)[0].charAt(0).toLowerCase(); - - component.label = `(${initial}) ${component.label}` - } - } - } - results.push(multiProfile) - } + let mergeComponents = function (results, groupsToMerge, title) { + if (groupsToMerge.length > 0) { + // we have to MERGE + let multiProfile = { + groups: {}, + groupsOrder: [], + label: title, + profileId: title, + type: title.includes("Default") ? 'default' : null + } - return results - } - - let r = mergeComponents(results, groupsToMerge, 'Multi') - results.concat(r) - r = mergeComponents(results, groupsToMergeDefault, 'Multi Default') - results.concat(r) - - //merge the defaults into 1 list - if (usePreferenceStore().returnValue('--b-edit-main-splitpane-properties-show-defaults')){ - let defaultIdx = [] - let defaults = [] - let defaultObj = {type: "default", groups:{}, groupsOrder:[], profileId: 'defaults', label: 'Defaults'} - // Get the defaults - for (let item in results){ - if (results[item].type == 'default'){ - defaultIdx.push(Number(item)) - defaults.push(results[item]) - } - } - //merge into 1 - for (let item of defaults){ - defaultObj.groups = Object.assign({}, defaultObj.groups, item.groups) - defaultObj.groupsOrder = defaultObj.groupsOrder.concat(item.groupsOrder) - } - //rebuild results - for (let i = results.length-1; i>=0; i--){ - if (defaultIdx.includes(i)){ - results.splice(i, 1) - } - } - let sortFn = function(a, b){ - let targetA = !defaultObj.groups[a][0].label.startsWith("(") ? defaultObj.groups[a][0].label : a - let targetB = !defaultObj.groups[b][0].label.startsWith("(") ? defaultObj.groups[b][0].label : b + for (let groupName of groupsToMerge) { + let tmpGroupComponents = [] + // remove them from the orginal group/profile and them to the multi profile + for (let profileComponents of results) { + if (profileComponents.groups[groupName]) { + tmpGroupComponents = tmpGroupComponents.concat(JSON.parse(JSON.stringify(profileComponents.groups[groupName]))) + delete profileComponents.groups[groupName] + } + profileComponents.groupsOrder = profileComponents.groupsOrder.filter((v) => { return (v !== groupName) }) + } - let val = targetA < targetB ? -1 : targetA > targetB ? 1 : 0 + // put them into the multi profile + multiProfile.groups[groupName] = tmpGroupComponents + multiProfile.groupsOrder.push(groupName) - return val - } - defaultObj.groupsOrder.sort(sortFn) + // add a label to denote if the individual component is a work or instance whatever component. + for (let groupKey in multiProfile.groups) { + for (let component of multiProfile.groups[groupKey]) { + if (component.label.indexOf("(i)") > -1) { continue } + if (component.label.indexOf("(w)") > -1) { continue } + let initial = component.structure.parentId.split(':').slice(-1)[0].charAt(0).toLowerCase(); - results.push(defaultObj) - } + component.label = `(${initial}) ${component.label}` + } + } + } + results.push(multiProfile) + } - // remove any empty ones that may have shifted fully into the multi profile - results = results.filter((g) => {return (g.groupsOrder.length>0)}) + return results + } - results = results.sort((a,b) => { - if (!a.type || a.type == null) { return -1} - if (!b.type || b.type == null) { return 1} - return (a.type < b.type) ? 1 : (a.type > b.type) ? -1 : 0 - }) - return results - }, + let r = mergeComponents(results, groupsToMerge, 'Multi') + results.concat(r) + r = mergeComponents(results, groupsToMergeDefault, 'Multi Default') + results.concat(r) + //merge the defaults into 1 list + if (usePreferenceStore().returnValue('--b-edit-main-splitpane-properties-show-defaults')) { + let defaultIdx = [] + let defaults = [] + let defaultObj = { type: "default", groups: {}, groupsOrder: [], profileId: 'defaults', label: 'Defaults' } + // Get the defaults + for (let item in results) { + if (results[item].type == 'default') { + defaultIdx.push(Number(item)) + defaults.push(results[item]) + } + } + //merge into 1 + for (let item of defaults) { + defaultObj.groups = Object.assign({}, defaultObj.groups, item.groups) + defaultObj.groupsOrder = defaultObj.groupsOrder.concat(item.groupsOrder) + } + //rebuild results + for (let i = results.length - 1; i >= 0; i--) { + if (defaultIdx.includes(i)) { + results.splice(i, 1) + } + } + let sortFn = function (a, b) { + let targetA = !defaultObj.groups[a][0].label.startsWith("(") ? defaultObj.groups[a][0].label : a + let targetB = !defaultObj.groups[b][0].label.startsWith("(") ? defaultObj.groups[b][0].label : b + let val = targetA < targetB ? -1 : targetA > targetB ? 1 : 0 + return val + } + defaultObj.groupsOrder.sort(sortFn) - }, - actions: { - resetLocalComponentCache(){ - cachePt = {} - cacheGuid = {} - dataChangedTimeout = null - }, + results.push(defaultObj) + } - /** Load the default component order */ - useDefaultComponentOrder(){ - let profileName = this.activeProfile.id - let profile = this.profiles[profileName] - - for (let profileName in profile.rt){ - let currentOrder = this.activeProfile.rt[profileName].ptOrder - let defaultOrder = profile.rt[profileName].ptOrder - let tempOrder = [] - for (let el of defaultOrder){ - // These should all be base level names, no `_ + new Date()` - let matchingComponents = currentOrder.filter(i => i.includes(el)) // keep like components together - tempOrder = tempOrder.concat(matchingComponents.sort()) - } + // remove any empty ones that may have shifted fully into the multi profile + results = results.filter((g) => { return (g.groupsOrder.length > 0) }) - //Need to get the admin fields - let components = Object.keys(this.activeProfile.rt[profileName].pt) - for (let c of components){ - if (!tempOrder.includes(c)){ - tempOrder.push(c) - } - } + results = results.sort((a, b) => { + if (!a.type || a.type == null) { return -1 } + if (!b.type || b.type == null) { return 1 } + return (a.type < b.type) ? 1 : (a.type > b.type) ? -1 : 0 + }) + return results + }, - this.activeProfile.rt[profileName].ptOrder = tempOrder - } - }, - /** Save the cataloger's current component order */ - saveCustomComponentOrder(){ - let order = {} - - let profileName = this.activeProfile.id - let profile = this.profiles[profileName] - - for (let rt in this.activeProfile.rt){ - let activeOrder = this.activeProfile.rt[rt].ptOrder - let frozenDefaultOrder = JSON.parse(JSON.stringify(profile.rt[rt].ptOrder)) - // the order should only have base names for components - // They'll be grouped together if there is more than 1 - let tempArray = [] - for (let el of activeOrder){ - if (!frozenDefaultOrder.includes(el)){ - let defaultMatch = frozenDefaultOrder.filter(item => el == item) - if (!tempArray.includes(defaultMatch[0])){ - tempArray.push(defaultMatch[0]) - } - } else { - tempArray.push(el) - } - } - order[rt] = tempArray - } - usePreferenceStore().saveOrder(order) - this.useCustomComponentOrder() }, + actions: { + resetLocalComponentCache() { + cachePt = {} + cacheGuid = {} + dataChangedTimeout = null + }, - /** Load the saved custom component order */ - useCustomComponentOrder(){ - let profileName = this.activeProfile.id - let profile = this.profiles[profileName] + /** Load the default component order */ + useDefaultComponentOrder() { + let profileName = this.activeProfile.id + let profile = this.profiles[profileName] + + for (let profileName in profile.rt) { + let currentOrder = this.activeProfile.rt[profileName].ptOrder + let defaultOrder = profile.rt[profileName].ptOrder + let tempOrder = [] + for (let el of defaultOrder) { + // These should all be base level names, no `_ + new Date()` + let matchingComponents = currentOrder.filter(i => i.includes(el)) // keep like components together + tempOrder = tempOrder.concat(matchingComponents.sort()) + } - let order = usePreferenceStore().loadOrder() + //Need to get the admin fields + let components = Object.keys(this.activeProfile.rt[profileName].pt) + for (let c of components) { + if (!tempOrder.includes(c)) { + tempOrder.push(c) + } + } - for (let profileName in order){ - if (!Object.keys(this.activeProfile.rt).includes(profileName)){ continue } - let currentOrder = this.activeProfile.rt[profileName].ptOrder - let customOrder = order[profileName] - let tempOrder = [] + this.activeProfile.rt[profileName].ptOrder = tempOrder + } + }, - // if there's a change to the profile, adjust the order accordingly - let profileOrder = profile.rt[profileName].ptOrder + /** Save the cataloger's current component order */ + saveCustomComponentOrder() { + let order = {} + + let profileName = this.activeProfile.id + let profile = this.profiles[profileName] + + for (let rt in this.activeProfile.rt) { + let activeOrder = this.activeProfile.rt[rt].ptOrder + let frozenDefaultOrder = JSON.parse(JSON.stringify(profile.rt[rt].ptOrder)) + // the order should only have base names for components + // They'll be grouped together if there is more than 1 + let tempArray = [] + for (let el of activeOrder) { + if (!frozenDefaultOrder.includes(el)) { + let defaultMatch = frozenDefaultOrder.filter(item => el == item) + if (!tempArray.includes(defaultMatch[0])) { + tempArray.push(defaultMatch[0]) + } + } else { + tempArray.push(el) + } + } - let additionalToCustomOrder = customOrder.filter(el => !profileOrder.includes(el)) - let missingFromCustomOrder = profileOrder.filter(el => !customOrder.includes(el)) + order[rt] = tempArray + } - // remove the extra pieces - customOrder = customOrder.filter(el => !additionalToCustomOrder.includes(el)) - // customOrder = customOrder.concat(missingFromCustomOrder) + usePreferenceStore().saveOrder(order) + this.useCustomComponentOrder() + }, - // add the missing peices in the same index from the default - for (let el of missingFromCustomOrder){ - let idx = profileOrder.indexOf(el) - customOrder.splice(idx, 0, el) - } + /** Load the saved custom component order */ + useCustomComponentOrder() { + let profileName = this.activeProfile.id + let profile = this.profiles[profileName] - for (let el of customOrder){ - // These should all be base level names, no `_ + new Date()` - let matchingComponents = currentOrder.filter(i => i.includes(el)) // keep like components together - tempOrder = tempOrder.concat(matchingComponents) //.sort() - } + let order = usePreferenceStore().loadOrder() - //Need to get the admin fields - let components = Object.keys(this.activeProfile.rt[profileName].pt) - for (let c of components){ - if (!tempOrder.includes(c)){ - tempOrder.push(c) - } - } + for (let profileName in order) { + if (!Object.keys(this.activeProfile.rt).includes(profileName)) { continue } + let currentOrder = this.activeProfile.rt[profileName].ptOrder + let customOrder = order[profileName] + let tempOrder = [] - this.activeProfile.rt[profileName].ptOrder = tempOrder - } - }, + // if there's a change to the profile, adjust the order accordingly + let profileOrder = profile.rt[profileName].ptOrder - /** - * The main first process that takes the raw profiles and processes them for use - * - * @return {void} - */ - async buildProfiles() { - const config = useConfigStore() - - let profilesURL = config.returnUrls.profiles - let startingURL = config.returnUrls.starting - - // if dancer is enabled check to see if they have a custom workspace set - if (config.returnUrls.dancerEnabled){ - // does the local storage have a custom workspace set - console.log("localStorage.getItem('marva-dancerWorkspace')",localStorage.getItem('marva-dancerWorkspace')) - if (localStorage.getItem('marva-dancerWorkspace') && localStorage.getItem('marva-dancerWorkspace') != "null"){ - let dancerWorkspace = localStorage.getItem('marva-dancerWorkspace') - // dancerWorkspace is now the UUID of the workspace, so we need to build the URI - let dancerBaseUrl = config.returnUrls.dancerWorkspaceList.split('workspaces')[0] - profilesURL = dancerBaseUrl + dancerWorkspace + '/profile' - startingURL = dancerBaseUrl + dancerWorkspace + '/starting-points' - } else if (config.returnUrls.isBibframeDotOrg) { - // no workspace set yet, fetch the workspace list and use marva-default - try { - let wsResponse = await fetch(config.returnUrls.dancerWorkspaceList) - let wsData = await wsResponse.json() - if (wsData.success && wsData.data) { - let defaultWs = wsData.data.find(ws => ws.name === 'marva-default') - if (defaultWs) { - localStorage.setItem('marva-dancerWorkspace', defaultWs.id) - let dancerBaseUrl = config.returnUrls.dancerWorkspaceList.split('workspaces')[0] - profilesURL = dancerBaseUrl + defaultWs.id + '/profile' - startingURL = dancerBaseUrl + defaultWs.id + '/starting-points' - } - } - } catch (err) { - console.error('Error fetching dancer workspace list:', err) - } - }else{ - try { - let wsResponse = await fetch(config.returnUrls.dancerWorkspaceList) - let wsData = await wsResponse.json() - console.log("wsData",wsData) - let defaultWs - // if we are stage try to find stage - if (config.returnUrls.env === 'staging') { - defaultWs = wsData.data.find(ws => ws.name === 'marva-stage') - } - if (config.returnUrls.env === 'production') { - defaultWs = wsData.data.find(ws => ws.name === 'marva-prod') - } - - if (defaultWs) { - let dancerBaseUrl = config.returnUrls.dancerWorkspaceList.split('workspaces')[0] - profilesURL = dancerBaseUrl + defaultWs.id + '/profile' - startingURL = dancerBaseUrl + defaultWs.id + '/starting-points' - } - - if (config.returnUrls.externalDev) { - profilesURL = config.returnUrls.profiles - startingURL = config.returnUrls.starting - - } - - - } catch (err) { - console.error('Error fetching dancer workspace list:', err) - } + let additionalToCustomOrder = customOrder.filter(el => !profileOrder.includes(el)) + let missingFromCustomOrder = profileOrder.filter(el => !customOrder.includes(el)) + // remove the extra pieces + customOrder = customOrder.filter(el => !additionalToCustomOrder.includes(el)) + // customOrder = customOrder.concat(missingFromCustomOrder) + // add the missing peices in the same index from the default + for (let el of missingFromCustomOrder) { + let idx = profileOrder.indexOf(el) + customOrder.splice(idx, 0, el) + } - } - } - - - let profileData; - try{ - let response = await fetch(profilesURL); - profileData = await response.json() - }catch(err){ - console.log("Error Downloading profiles from:", config.returnUrls.profiles) - - alert('Could not download the profiles, unable to continue.') - console.error(err); - } - - - let startingPointData; - - - try{ - let response = await fetch(startingURL); - startingPointData = await response.json() - }catch(err){ - console.log("Error Downloading Starting Points from:", config.returnUrls.starting) - alert('Could not download the starting points, unable to continue.') - console.error(err); - } - - // FLAG: NEEDS_PROFILE_ALIGNMENT - // TEMP HACK ADD IN HUBS - - if (startingPointData[0]){ - startingPointData[0].json.push( - { - "menuGroup": "Hub", - "menuItems": [ - { - "label": "Hub", - "type": [ - "http://id.loc.gov/ontologies/bibframe/Hub" - ], - "useResourceTemplates": [ - "lc:RT:bf2:Hub:Hub" - ] - } - ] - } - ) - }else if (startingPointData.json){ - startingPointData.json.push( - { - "menuGroup": "Hub", - "menuItems": [ - { - "label": "Hub", - "type": [ - "http://id.loc.gov/ontologies/bibframe/Hub" - ], - "useResourceTemplates": [ - "lc:RT:bf2:Hub:Hub" - ] - } - ] - } - ) - } - // FLAG: NEEDS_PROFILE_ALIGNMENT - // TEMP HACK, striping RDA fields for some things for the new editor - for (let p of profileData){ - - if (p.json.Profile.id == 'lc:profile:bf2:Agents:Attributes'){ - - for (let rt of p.json.Profile.resourceTemplates){ - if (['lc:RT:bf2:Agent:Person','lc:RT:bf2:Agent:Family','lc:RT:bf2:Agent:CorporateBody','lc:RT:bf2:Agent:Conference','lc:RT:bf2:Agent:Jurisdiction'].indexOf(rt.id)>-1){ - rt.propertyTemplates = [rt.propertyTemplates[0]] - } - } + for (let el of customOrder) { + // These should all be base level names, no `_ + new Date()` + let matchingComponents = currentOrder.filter(i => i.includes(el)) // keep like components together + tempOrder = tempOrder.concat(matchingComponents) //.sort() + } - } + //Need to get the admin fields + let components = Object.keys(this.activeProfile.rt[profileName].pt) + for (let c of components) { + if (!tempOrder.includes(c)) { + tempOrder.push(c) + } + } - // remove certin properties from the RTs - p.json.Profile.resourceTemplates = p.json.Profile.resourceTemplates.filter((rt)=>{ - rt.propertyTemplates = rt.propertyTemplates.filter((pt)=>{ - if (pt.propertyLabel && (pt.propertyLabel.startsWith('Input RDA relationship designator ter') || - pt.propertyLabel.startsWith('Input Geographic Coverage (if not on list)'))){ - return false - } + this.activeProfile.rt[profileName].ptOrder = tempOrder + } + }, - if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/adminMetadata'){ - return false - } + /** + * The main first process that takes the raw profiles and processes them for use + * + * @return {void} + */ + async buildProfiles() { + const config = useConfigStore() + + let profilesURL = config.returnUrls.profiles + let startingURL = config.returnUrls.starting + + // if dancer is enabled check to see if they have a custom workspace set + if (config.returnUrls.dancerEnabled) { + // does the local storage have a custom workspace set + console.log("localStorage.getItem('marva-dancerWorkspace')", localStorage.getItem('marva-dancerWorkspace')) + if (localStorage.getItem('marva-dancerWorkspace') && localStorage.getItem('marva-dancerWorkspace') != "null") { + let dancerWorkspace = localStorage.getItem('marva-dancerWorkspace') + // dancerWorkspace is now the UUID of the workspace, so we need to build the URI + let dancerBaseUrl = config.returnUrls.dancerWorkspaceList.split('workspaces')[0] + profilesURL = dancerBaseUrl + dancerWorkspace + '/profile' + startingURL = dancerBaseUrl + dancerWorkspace + '/starting-points' + } else if (config.returnUrls.isBibframeDotOrg) { + // no workspace set yet, fetch the workspace list and use marva-default + try { + let wsResponse = await fetch(config.returnUrls.dancerWorkspaceList) + let wsData = await wsResponse.json() + if (wsData.success && wsData.data) { + let defaultWs = wsData.data.find(ws => ws.name === 'marva-default') + if (defaultWs) { + localStorage.setItem('marva-dancerWorkspace', defaultWs.id) + let dancerBaseUrl = config.returnUrls.dancerWorkspaceList.split('workspaces')[0] + profilesURL = dancerBaseUrl + defaultWs.id + '/profile' + startingURL = dancerBaseUrl + defaultWs.id + '/starting-points' + } + } + } catch (err) { + console.error('Error fetching dancer workspace list:', err) + } + } else { + try { + let wsResponse = await fetch(config.returnUrls.dancerWorkspaceList) + let wsData = await wsResponse.json() + console.log("wsData", wsData) + let defaultWs + // if we are stage try to find stage + if (config.returnUrls.env === 'staging') { + defaultWs = wsData.data.find(ws => ws.name === 'marva-stage') + } + if (config.returnUrls.env === 'production') { + defaultWs = wsData.data.find(ws => ws.name === 'marva-prod') + } - return true - }) + if (defaultWs) { + let dancerBaseUrl = config.returnUrls.dancerWorkspaceList.split('workspaces')[0] + profilesURL = dancerBaseUrl + defaultWs.id + '/profile' + startingURL = dancerBaseUrl + defaultWs.id + '/starting-points' + } - if (rt){ - return true - } + if (config.returnUrls.externalDev) { + profilesURL = config.returnUrls.profiles + startingURL = config.returnUrls.starting + } - }) - // modify specific PT values - for (let rt of p.json.Profile.resourceTemplates){ + } catch (err) { + console.error('Error fetching dancer workspace list:', err) + } - if (config.profileHacks.profileParseFixLowerCaseContribution.enabled){ - if (rt.id === 'lc:RT:bf2:Agents:contribution'){ - rt.id = 'lc:RT:bf2:Agents:Contribution' - } - } - // modify the subject headings to match the new editor - if (rt.id == 'lc:RT:bf2:Components'){ - for (let pt of rt.propertyTemplates){ - pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((ref)=>{if (ref == 'lc:RT:bf2:Topic:madsTopic'){ return true}}) - } - } - if (rt.id == 'lc:RT:bf2:Topic:Place:Components'){ - for (let pt of rt.propertyTemplates){ - pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((ref)=>{if (ref == 'lc:RT:bf2:Topic:madsGeogHeading'){ return true}}) - } - } - if (rt.id == 'lc:RT:bf2:Topic:Childrens:Components'){ - for (let pt of rt.propertyTemplates){ - pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((ref)=>{if (ref == 'lc:RT:bf2:Topic:Childrens:Topic'){ return true}}) - } - } + } + } - if (rt.id == 'lc:RT:bf2:Brief:Work'){ - rt.propertyTemplates = [ - { - "mandatory": "false", - "propertyLabel": "Lookup", - "propertyURI": "http://id.loc.gov/ontologies/bibframe/Work", - "repeatable": "true", - "resourceTemplates": [], - "type": "lookup", - "valueConstraint": { - "defaults": [], - "useValuesFrom": [ - "https://preprod-8230.id.loc.gov/resources/works" - ], - "valueDataType": { - "dataTypeURI": "" - }, - "valueTemplateRefs": [] - } - }] + let profileData; + try { + let response = await fetch(profilesURL); + profileData = await response.json() + } catch (err) { + console.log("Error Downloading profiles from:", config.returnUrls.profiles) - } + alert('Could not download the profiles, unable to continue.') + console.error(err); + } - if (rt.id == 'lc:RT:bf2:GPORelWorkBrief'){ - rt.propertyTemplates = [ - { - "mandatory": "false", - "propertyLabel": "Lookup", - "propertyURI": "http://id.loc.gov/ontologies/bibframe/Work", - "repeatable": "true", - "resourceTemplates": [], - "type": "lookup", - "valueConstraint": { - "defaults": [], - "useValuesFrom": [ - "https://preprod-8295.id.loc.gov/resources/works" - ], - "valueDataType": { - "dataTypeURI": "" - }, - "valueTemplateRefs": [] - } - }] - } + let startingPointData; - if (rt.id == 'lc:RT:bf2:Brief:Instance'){ - rt.propertyTemplates = [ - { - "mandatory": "false", - "propertyLabel": "Lookup", - "propertyURI": "http://id.loc.gov/ontologies/bibframe/Instance", - "repeatable": "true", - "resourceTemplates": [], - "type": "lookup", - "valueConstraint": { - "defaults": [], - "useValuesFrom": [ - "https://preprod-8230.id.loc.gov/resources/instances" - ], - "valueDataType": { - "dataTypeURI": "" - }, - "valueTemplateRefs": [] - } - }] - - } - - // add wikidata into any NAF lookup pt - if (rt.id.includes(':Agent')){ - for (let pt of rt.propertyTemplates){ - if (pt.valueConstraint.useValuesFrom.indexOf('http://preprod.id.loc.gov/authorities/names')>-1){ - if (pt.valueConstraint.useValuesFrom.indexOf('https://www.wikidata.org/w/api.php')==-1){ - pt.valueConstraint.useValuesFrom.push('https://www.wikidata.org/w/api.php') - } - } - } - } - - - for (let pt of rt.propertyTemplates){ - - if (config.profileHacks.profileParseFixPropertyURIWhenUpperCase.enabled){ - - // something to think about...? - if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Role'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/role' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Frequency'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/frequency' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/ProductionMethod'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/productionMethod' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/GrooveCharacteristic'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/soundCharacteristic' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/MusicNotation'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/notation' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bflc/Relation'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bflc/relation' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Scale'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/scale' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Frequency'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/frequency' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Place'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/place' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Illustration'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/illustrativeContent' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/PlaybackChannels'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/soundCharacteristic' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/RecordingMethod'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/soundCharacteristic' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/BroadcastStandard'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/videoCharacteristic' - // }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bflc/CaptureStorage'){ - // pt.propertyURI = 'http://id.loc.gov/ontologies/bflc/captureStorage' - - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/ColorContent'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/colorContent' - - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/ColorContent'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/colorContent' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/EncodingFormat'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/digitalCharacteristic' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/FileType'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/digitalCharacteristic' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/IntendedAudience'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/intendedAudience' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/PresentationFormat'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/projectionCharacteristic' - - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Language'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/language' - }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/SupplementaryContent'){ - pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/supplementaryContent' - - } - - // others? - // "propertyURI": "http://id.loc.gov/ontologies/bflc/MovingImageTechnique", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/Generation", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/RecordingMedium", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/RegionalEncoding", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/SoundContent", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/PlaybackCharacteristic", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/MusicInstrument", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/MusicVoice", - // "propertyURI": "http://id.loc.gov/ontologies/bibframe/MusicEnsemble", - } - } - } - } - - // -------- end HACKKCKCKCKCK - profileData.forEach((p)=>{ - - - // build the first level profiles - if (p.json && p.json.Profile){ - - // for example monograph -> work - this.profiles[p.json.Profile.id] = { - rtOrder: [], - rt: {}, - id: p.json.Profile.id - } - // now make obj of all the properties in each top level - // for example monograph -> work -> title - if (p.json.Profile.resourceTemplates){ - - p.json.Profile.resourceTemplates.forEach((rt)=>{ - this.profiles[p.json.Profile.id].rtOrder.push(rt.id) - this.profiles[p.json.Profile.id].rt[rt.id] = {ptOrder:[],pt:{}} - if (rt.propertyTemplates){ - rt.propertyTemplates.forEach((pt)=>{ - pt.parent = p.json.Profile.id + rt.id + p.id - pt.parentId = rt.id - pt.userValue = {'@root':pt.propertyURI} - pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((v)=>{return (v.length>0)}) - pt['@guid'] = short.generate() - pt.canBeHidden = true - - if (pt.type === 'literal-lang'){ - this.profiles[p.json.Profile.id].rt[rt.id].hasLiteralLangFields = true - } - - // try to make a profile wide unique identifier to hang preferences off of for that property - let propUniqueId = null - if (pt.propertyURI.includes("/note")){ - propUniqueId = pt.propertyURI + "|" + pt.parentId - }else if (pt.valueConstraint && pt.valueConstraint.valueDataType && pt.valueConstraint.valueDataType.dataTypeURI){ - propUniqueId = pt.propertyURI + "|" + pt.valueConstraint.valueDataType.dataTypeURI - }else if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length>0 ){ - propUniqueId = pt.propertyURI + "|" + pt.valueConstraint.valueTemplateRefs[0] - }else if (pt.valueConstraint && pt.valueConstraint.useValuesFrom && pt.valueConstraint.useValuesFrom.length>0 ){ - propUniqueId = pt.propertyURI + "|" + pt.valueConstraint.useValuesFrom[0] - }else{ - propUniqueId = pt.propertyURI + "|" + pt.parentId - } - - pt.preferenceId = propUniqueId - let key = pt.propertyURI.replace('http://','').replace('https://','').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"_") + '__' + ((pt.propertyLabel) ? pt.propertyLabel.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g,'_').toLowerCase() : "plabel") - this.profiles[p.json.Profile.id].rt[rt.id].ptOrder.push(key) - this.profiles[p.json.Profile.id].rt[rt.id].pt[key] = pt - this.profiles[p.json.Profile.id].rt[rt.id].pt[key].id = key - - }) - } - }) - } - - } - - // loop through each profile and extract the resource template, save them as their own look up for later use - if (p.json && p.json.Profile && p.json.Profile.resourceTemplates){ - p.json.Profile.resourceTemplates.forEach((rt)=>{ - this.rtLookup[rt.id] = rt - }) - } - }) - - // make a copy of the obj to cut refs to the orginal - // this.profiles = Object.assign({}, this.profiles) - //this.profiles = JSON.parse(JSON.stringify(this.profiles)) - - - - - - // make a lookup for just the profiles rts - let plookup = {} - for (let p of Object.keys(this.profiles)){ - this.profiles[p].rtOrder.forEach((rtname)=>{ - plookup[rtname] = this.profiles[p].rt[rtname] - }) - } - - // we have starting points, generate those profiles based on their requirements - // this is just converting the format of the old BFE starting points format - if (Array.isArray(startingPointData)){ - startingPointData = startingPointData[0] - } - - - - // // removed march 2026 - // if (config.returnUrls.env != 'production'){ - // startingPointData.json.splice(2,0,{ - // "menuGroup": "GPO Monograph", - // "menuItems": [ - // { - // "label": "Instance", - // "type": [ - // "http://id.loc.gov/ontologies/bibframe/Instance" - // ], - // "useResourceTemplates": [ - // "lc:RT:bf2:GPOMono:Instance" - // ] - // }, - // { - // "label": "Work", - // "type": [ - // "http://id.loc.gov/ontologies/bibframe/Work" - // ], - // "useResourceTemplates": [ - // "lc:RT:bf2:GPOMono:Work" - // ] - // } - // ] - // }) - // startingPointData.json.splice(3,0,{ - // "menuGroup": "GPO Serial", - // "menuItems": [ - // { - // "label": "Instance", - // "type": [ - // "http://id.loc.gov/ontologies/bibframe/Instance" - // ], - // "useResourceTemplates": [ - // "lc:RT:bf2:GPOSerial:Instance" - // ] - // }, - // { - // "label": "Work", - // "type": [ - // "http://id.loc.gov/ontologies/bibframe/Work" - // ], - // "useResourceTemplates": [ - // "lc:RT:bf2:GPOSerial:Work" - // ] - // } - // ] - // }) - // } - - - startingPointData.json.forEach((sp)=>{ - - this.startingPoints[sp.menuGroup] = {name:sp.menuGroup, work: null, instance: null, item: null } - sp.menuItems.forEach((mi)=>{ - - if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Instance')>-1){ - - this.startingPoints[sp.menuGroup].instance = mi.useResourceTemplates[0] - } - if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Work')>-1){ - - this.startingPoints[sp.menuGroup].work = mi.useResourceTemplates[0] - } - if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Item')>-1){ - - this.startingPoints[sp.menuGroup].item = mi.useResourceTemplates[0] - } - if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Hub')>-1){ - - this.startingPoints[sp.menuGroup].hub = mi.useResourceTemplates[0] - } - - - }) - - // create a "profile" for this starting point in our profiles - this.profiles[sp.menuGroup] ={ id: sp.menuGroup, rt: {}, rtOrder : [] } - if (this.startingPoints[sp.menuGroup].hub){ - this.profiles[sp.menuGroup].rt[this.startingPoints[sp.menuGroup].hub] = plookup[this.startingPoints[sp.menuGroup].hub] - this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].hub) - } - if (this.startingPoints[sp.menuGroup].work){ - this.profiles[sp.menuGroup].rt[this.startingPoints[sp.menuGroup].work] = plookup[this.startingPoints[sp.menuGroup].work] - this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].work) - } - - - if (this.startingPoints[sp.menuGroup].instance){ - this.profiles[sp.menuGroup].rt[this.startingPoints[sp.menuGroup].instance] = plookup[this.startingPoints[sp.menuGroup].instance] - this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].instance) - } - - // if there is a hub and work and instance then always put the hub at the start - if (this.startingPoints[sp.menuGroup].hub && this.startingPoints[sp.menuGroup].work && this.startingPoints[sp.menuGroup].instance){ - - this.profiles[sp.menuGroup].rtOrder = [] - this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].hub) - this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].work) - this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].instance) - } - - - - - - }) - - for (let p in this.profiles){ - this.profiles[p].hashRts = {} - this.profiles[p].hashPts = {} - - for (let rt in this.profiles[p].rt){ - if (this.profiles[p].rt[rt]){ - this.profiles[p].hashRts[rt] = this.hashRt(this.profiles[p].rt[rt]) - } - - if (this.profiles[p].rt[rt] && this.profiles[p].rt[rt].pt){ - for (let pt in this.profiles[p].rt[rt].pt){ - // build the key to the property - let id = rt + '|' + this.profiles[p].rt[rt].pt[pt].propertyURI - if (this.profiles[p].rt[rt].pt[pt].valueConstraint && this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType && this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType.dataTypeURI && this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType.dataTypeURI.trim() != ''){ - id = id + '|' + this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType.dataTypeURI - } - // builds an id like this: lc:RT:bf2:35mmFeatureFilm:Work|http://id.loc.gov/ontologies/bibframe/contribution|http://id.loc.gov/ontologies/bflc/PrimaryContribution - let ptVal = JSON.parse(JSON.stringify(this.profiles[p].rt[rt].pt[pt])) - // ensure reliable order - ptVal.valueConstraint = Object.keys(ptVal.valueConstraint).sort().reduce( - (obj, key) => { - obj[key] = ptVal.valueConstraint[key]; - return obj; - }, - {} - ); - - delete ptVal['@guid'] - delete ptVal['hashCode'] - delete ptVal['hashCodeId'] - delete ptVal['userValue'] - delete ptVal['canBeHidden'] - delete ptVal['dataLoaded'] - delete ptVal['deepHierarchy'] - delete ptVal['hasData'] - delete ptVal['remark'] - delete ptVal['parent'] - delete ptVal['xmlHash'] - delete ptVal['xmlSource'] - delete ptVal['userModified'] - delete ptVal['activeType'] - delete ptVal['missingProfile'] - delete ptVal['valueConstraint']['editable'] - delete ptVal['valueConstraint']['repeatable'] - delete ptVal['valueConstraint']['defaults'] - - try{ - delete ptVal['valueConstraint']['valueDataType']['dataTypeLabel'] - delete ptVal['valueConstraint']['valueDataType']['remark'] - } catch { } - - let orderedPtVal = Object.keys(ptVal).sort().reduce( - (obj, key) => { - obj[key] = ptVal[key]; - return obj; - }, - {} - ); - this.profiles[p].hashPts[id] = hashCode(JSON.stringify(orderedPtVal)) - this.profiles[p].rt[rt].pt[pt].hashCode = hashCode(JSON.stringify(orderedPtVal)) - this.profiles[p].rt[rt].pt[pt].hashCodeId = id - } + try { + let response = await fetch(startingURL); + startingPointData = await response.json() + } catch (err) { + console.log("Error Downloading Starting Points from:", config.returnUrls.starting) + alert('Could not download the starting points, unable to continue.') + console.error(err); + } - } - } - } - // console.log('this.profiles',this.profiles) - this.profilesLoaded = true - // return { profiles: this.profiles, lookup: this.rtLookup, startingPoints: this.startingPoints} + // FLAG: NEEDS_PROFILE_ALIGNMENT + // TEMP HACK ADD IN HUBS + if (startingPointData[0]) { + startingPointData[0].json.push( + { + "menuGroup": "Hub", + "menuItems": [ + { + "label": "Hub", + "type": [ + "http://id.loc.gov/ontologies/bibframe/Hub" + ], + "useResourceTemplates": [ + "lc:RT:bf2:HubBasic:Hub" //"lc:RT:bf2:Hub:Hub" + ] + } + ] + } + ) + } else if (startingPointData.json) { + startingPointData.json.push( + { + "menuGroup": "Hub", + "menuItems": [ + { + "label": "Hub", + "type": [ + "http://id.loc.gov/ontologies/bibframe/Hub" + ], + "useResourceTemplates": [ + "lc:RT:bf2:HubBasic:Hub" //"lc:RT:bf2:Hub:Hub" + ] + } + ] + } + ) + } + // FLAG: NEEDS_PROFILE_ALIGNMENT + // TEMP HACK, striping RDA fields for some things for the new editor + for (let p of profileData) { - }, + if (p.json.Profile.id == 'lc:profile:bf2:Agents:Attributes') { - /** - * Takes a resource template from the profile and creates a hash of the json-fied version of it to use as a fingerprint, to see if it changes later - * For use in "template" functionality, to know if a resource template has changed since a template has used it - * - * @param {object} rt - the rt from the profile - * @return {number} The fingerprint hash - */ - hashRt: function(rt){ - rt = JSON.parse(JSON.stringify(rt)) - for (let pt in rt.pt){ - delete rt.pt[pt]['@guid'] - } - return hashCode(JSON.stringify(rt)) - }, + for (let rt of p.json.Profile.resourceTemplates) { + if (['lc:RT:bf2:Agent:Person', 'lc:RT:bf2:Agent:Family', 'lc:RT:bf2:Agent:CorporateBody', 'lc:RT:bf2:Agent:Conference', 'lc:RT:bf2:Agent:Jurisdiction'].indexOf(rt.id) > -1) { + rt.propertyTemplates = [rt.propertyTemplates[0]] + } + } + } + // remove certin properties from the RTs + p.json.Profile.resourceTemplates = p.json.Profile.resourceTemplates.filter((rt) => { + rt.propertyTemplates = rt.propertyTemplates.filter((pt) => { + if (pt.propertyLabel && (pt.propertyLabel.startsWith('Input RDA relationship designator ter') || + pt.propertyLabel.startsWith('Input Geographic Coverage (if not on list)'))) { + return false + } + if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/adminMetadata') { + return false + } - /** - * does all the work to setup a new profile read to be edited and posted as new resource - * - * @param {string} useStartingPoint - the name of the starting point to use to as the profile - * @param {string} addAdmin - the caloger user id to use in the admin metadata, if undefined it will not add a adminMetadata - * @param {object} userTemplateSupplied - a specific profile is being asked to be prepared, so don't look it up in the starting points, just use this one - * @return {object} useProfile - the profile modified ready to bet set in the state and edited - */ - loadNewTemplate(useStartingPoint,addAdmin,userTemplateSupplied){ + return true + }) - const config = useConfigStore() + if (rt) { + return true + } - // should be the catinitals to insert into admin if being passed - if (typeof addAdmin === 'undefined'){ - addAdmin=false - } - let useProfile + }) - if (userTemplateSupplied){ - useProfile = userTemplateSupplied - }else{ - useProfile = JSON.parse(JSON.stringify(this.profiles[useStartingPoint])) - } + // modify specific PT values + for (let rt of p.json.Profile.resourceTemplates) { - // some profiles have nested components at the root level used in that component - // so if it doesn't end with one of the main type of resources we want to edit - // then we don't want to render it, since it is probably being used in the main RT somewhere - let toRemove = [] - let toKeep = [] - for (let rt of useProfile.rtOrder){ - let hasTLS = false - for (let TLS of config.validTopLevelProfileSufixes){ - if (rt.includes(TLS)){ - hasTLS = true - } - } + if (config.profileHacks.profileParseFixLowerCaseContribution.enabled) { + if (rt.id === 'lc:RT:bf2:Agents:contribution') { + rt.id = 'lc:RT:bf2:Agents:Contribution' + } + } - if (hasTLS === false){ - toRemove.push(rt) - }else{ - toKeep.push(rt) - } - } + // modify the subject headings to match the new editor + if (rt.id == 'lc:RT:bf2:Components') { + for (let pt of rt.propertyTemplates) { + pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((ref) => { if (ref == 'lc:RT:bf2:Topic:madsTopic') { return true } }) + } + } + if (rt.id == 'lc:RT:bf2:Topic:Place:Components') { + for (let pt of rt.propertyTemplates) { + pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((ref) => { if (ref == 'lc:RT:bf2:Topic:madsGeogHeading') { return true } }) + } + } + if (rt.id == 'lc:RT:bf2:Topic:Childrens:Components') { + for (let pt of rt.propertyTemplates) { + pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((ref) => { if (ref == 'lc:RT:bf2:Topic:Childrens:Topic') { return true } }) + } + } - for (let rt of toRemove){ - delete useProfile.rt[rt] - } - useProfile.rtOrder = toKeep + if (rt.id == 'lc:RT:bf2:Brief:Work') { + rt.propertyTemplates = [ + { + "mandatory": "false", + "propertyLabel": "Lookup", + "propertyURI": "http://id.loc.gov/ontologies/bibframe/Work", + "repeatable": "true", + "resourceTemplates": [], + "type": "lookup", + "valueConstraint": { + "defaults": [], + "useValuesFrom": [ + "https://preprod-8230.id.loc.gov/resources/works" + ], + "valueDataType": { + "dataTypeURI": "" + }, + "valueTemplateRefs": [] + } + }] + } - if (!useProfile.log){ - useProfile.log=[] - } - useProfile.log.push({action:'createWorkInstance'}) - useProfile.procInfo= config.procInfoNewWorkInstance + if (rt.id == 'lc:RT:bf2:GPORelWorkBrief') { + rt.propertyTemplates = [ + { + "mandatory": "false", + "propertyLabel": "Lookup", + "propertyURI": "http://id.loc.gov/ontologies/bibframe/Work", + "repeatable": "true", + "resourceTemplates": [], + "type": "lookup", + "valueConstraint": { + "defaults": [], + "useValuesFrom": [ + "https://preprod-8295.id.loc.gov/resources/works" + ], + "valueDataType": { + "dataTypeURI": "" + }, + "valueTemplateRefs": [] + } + }] + } - // also give it an ID for storage - if (!useProfile.eId){ - // let uuid = 'e' + decimalTranslator.new() - // uuid = uuid.substring(0,8) - let uuid = 'e' + Date.now().toString() - useProfile.eId= uuid + if (rt.id == 'lc:RT:bf2:Brief:Instance') { + rt.propertyTemplates = [ + { + "mandatory": "false", + "propertyLabel": "Lookup", + "propertyURI": "http://id.loc.gov/ontologies/bibframe/Instance", + "repeatable": "true", + "resourceTemplates": [], + "type": "lookup", + "valueConstraint": { + "defaults": [], + "useValuesFrom": [ + "https://preprod-8230.id.loc.gov/resources/instances" + ], + "valueDataType": { + "dataTypeURI": "" + }, + "valueTemplateRefs": [] + } + }] - } + } - if (!useProfile.user){ - useProfile.user = usePreferenceStore().returnUserNameForSaving - } + // add wikidata into any NAF lookup pt + if (rt.id.includes(':Agent')) { + for (let pt of rt.propertyTemplates) { + if (pt.valueConstraint.useValuesFrom.indexOf('http://preprod.id.loc.gov/authorities/names') > -1) { + if (pt.valueConstraint.useValuesFrom.indexOf('https://www.wikidata.org/w/api.php') == -1) { + pt.valueConstraint.useValuesFrom.push('https://www.wikidata.org/w/api.php') + } + } + } + } - if (!useProfile.status){ - useProfile.status = 'unposted' - } - let workUri = null - let workUriId = translator.toUUID(translator.new()) + for (let pt of rt.propertyTemplates) { + + if (config.profileHacks.profileParseFixPropertyURIWhenUpperCase.enabled) { + + // something to think about...? + if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Role') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/role' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Frequency') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/frequency' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/ProductionMethod') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/productionMethod' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/GrooveCharacteristic') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/soundCharacteristic' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/MusicNotation') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/notation' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bflc/Relation') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bflc/relation' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Scale') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/scale' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Frequency') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/frequency' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Place') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/place' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Illustration') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/illustrativeContent' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/PlaybackChannels') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/soundCharacteristic' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/RecordingMethod') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/soundCharacteristic' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/BroadcastStandard') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/videoCharacteristic' + // }else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bflc/CaptureStorage'){ + // pt.propertyURI = 'http://id.loc.gov/ontologies/bflc/captureStorage' + + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/ColorContent') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/colorContent' + + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/ColorContent') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/colorContent' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/EncodingFormat') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/digitalCharacteristic' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/FileType') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/digitalCharacteristic' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/IntendedAudience') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/intendedAudience' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/PresentationFormat') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/projectionCharacteristic' + + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/Language') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/language' + } else if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/SupplementaryContent') { + pt.propertyURI = 'http://id.loc.gov/ontologies/bibframe/supplementaryContent' - for (let rt in useProfile.rt){ - let uri = null + } - // make a new uri for each one - if (rt.endsWith(config.validTopLevelWork)){ - uri = config.baseURIWork + workUriId - workUri = uri - }else if (rt.endsWith(config.validTopLevelInstance)){ + // others? + // "propertyURI": "http://id.loc.gov/ontologies/bflc/MovingImageTechnique", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/Generation", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/RecordingMedium", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/RegionalEncoding", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/SoundContent", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/PlaybackCharacteristic", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/MusicInstrument", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/MusicVoice", + // "propertyURI": "http://id.loc.gov/ontologies/bibframe/MusicEnsemble", + } + } + } + } - // when making a new instance from scratch use the work URI Id peice as the instance ID piece - uri = config.baseURIInstance + workUriId + // -------- end HACKKCKCKCKCK + profileData.forEach((p) => { - }else if (rt.endsWith(config.validTopLevelItem)){ - uri = config.baseURIItem + translator.toUUID(translator.new()) - }else if (rt.endsWith(config.validTopLevelHub)){ - uri = config.baseURIHub + translator.toUUID(translator.new()) - }else{ - // dunno what this is give it a random uri - uri = 'http://id.loc.gov/resources/unknown/' + translator.toUUID(translator.new()) - } - useProfile.rt[rt].URI = uri + // build the first level profiles + if (p.json && p.json.Profile) { - for (let pt in useProfile.rt[rt].pt){ + // for example monograph -> work + this.profiles[p.json.Profile.id] = { + rtOrder: [], + rt: {}, + id: p.json.Profile.id + } + // now make obj of all the properties in each top level + // for example monograph -> work -> title + if (p.json.Profile.resourceTemplates) { + + p.json.Profile.resourceTemplates.forEach((rt) => { + this.profiles[p.json.Profile.id].rtOrder.push(rt.id) + this.profiles[p.json.Profile.id].rt[rt.id] = { ptOrder: [], pt: {} } + if (rt.propertyTemplates) { + rt.propertyTemplates.forEach((pt) => { + pt.parent = p.json.Profile.id + rt.id + p.id + pt.parentId = rt.id + pt.userValue = { '@root': pt.propertyURI } + pt.valueConstraint.valueTemplateRefs = pt.valueConstraint.valueTemplateRefs.filter((v) => { return (v.length > 0) }) + pt['@guid'] = short.generate() + pt.canBeHidden = true + + if (pt.type === 'literal-lang') { + this.profiles[p.json.Profile.id].rt[rt.id].hasLiteralLangFields = true + } + + // try to make a profile wide unique identifier to hang preferences off of for that property + let propUniqueId = null + if (pt.propertyURI.includes("/note")) { + propUniqueId = pt.propertyURI + "|" + pt.parentId + } else if (pt.valueConstraint && pt.valueConstraint.valueDataType && pt.valueConstraint.valueDataType.dataTypeURI) { + propUniqueId = pt.propertyURI + "|" + pt.valueConstraint.valueDataType.dataTypeURI + } else if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length > 0) { + propUniqueId = pt.propertyURI + "|" + pt.valueConstraint.valueTemplateRefs[0] + } else if (pt.valueConstraint && pt.valueConstraint.useValuesFrom && pt.valueConstraint.useValuesFrom.length > 0) { + propUniqueId = pt.propertyURI + "|" + pt.valueConstraint.useValuesFrom[0] + } else { + propUniqueId = pt.propertyURI + "|" + pt.parentId + } + + pt.preferenceId = propUniqueId + let key = pt.propertyURI.replace('http://', '').replace('https://', '').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "_") + '__' + ((pt.propertyLabel) ? pt.propertyLabel.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "").replace(/\s+/g, '_').toLowerCase() : "plabel") + this.profiles[p.json.Profile.id].rt[rt.id].ptOrder.push(key) + this.profiles[p.json.Profile.id].rt[rt.id].pt[key] = pt + this.profiles[p.json.Profile.id].rt[rt.id].pt[key].id = key + + }) + } + }) + } - if (useProfile.rt[rt].pt[pt].propertyURI == "http://id.loc.gov/ontologies/bibframe/Work"){ - useProfile.rt[rt].pt[pt].userValue={ - '@root': 'http://id.loc.gov/ontologies/bibframe/Work', - '@guid': short.generate() , - '@id': uri, - } - } - } + } - } + // loop through each profile and extract the resource template, save them as their own look up for later use + if (p.json && p.json.Profile && p.json.Profile.resourceTemplates) { + p.json.Profile.resourceTemplates.forEach((rt) => { + this.rtLookup[rt.id] = rt + }) + } + }) + // make a copy of the obj to cut refs to the orginal + // this.profiles = Object.assign({}, this.profiles) + //this.profiles = JSON.parse(JSON.stringify(this.profiles)) - // apply the data we gathered / created above - for (let rt in useProfile.rt){ - if (rt.includes(config.validTopLevelWork)){ - // something - }else if (rt.includes(config.validTopLevelInstance)){ - //something - useProfile.rt[rt].instanceOf = workUri - }else if (rt.includes(config.validTopLevelItem)){ - //something - } - } - - // console.log('------useProfile-------') - // console.log(useProfile) - - if (addAdmin){ - for (let rt in useProfile.rt){ - let adminMetadataProperty = { - "mandatory": false, - "propertyLabel": "Admin Metadata", - "propertyURI": "http://id.loc.gov/ontologies/bibframe/adminMetadata", - "repeatable": false, - "resourceTemplates": [], - '@guid': short.generate(), - "type": "resource", - "userValue": { - "@root":"http://id.loc.gov/ontologies/bibframe/adminMetadata", - "http://id.loc.gov/ontologies/bibframe/adminMetadata":[{ - - "@type": "http://id.loc.gov/ontologies/bibframe/AdminMetadata", - '@guid': short.generate(), - "http://id.loc.gov/ontologies/bflc/catalogerId": [ - { - "@guid": short.generate(), - "http://id.loc.gov/ontologies/bflc/catalogerId": addAdmin - } - ] - }] - }, - "valueConstraint": { - "defaults": [], - "useValuesFrom": [], - "valueDataType": {}, - "valueTemplateRefs": [ (!rt.includes(':GPO')) ? 'lc:RT:bf2:AdminMetadata:BFDB' : 'lc:RT:bf2:GPOMono:AdminMetadata' ] - } - } - let adminMetadataPropertyLabel = 'http://id.loc.gov/ontologies/bibframe/adminMetadata'.replace('http://','').replace('https://','').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"_") + '__admin_metadata' - - if (useProfile.rt[rt].pt[adminMetadataPropertyLabel]){ - // console.log("Admin already exists for ", rt, 'chahhing userid') - // it already exists, so update the catalogerId and use the existing userValue - - if (useProfile.rt[rt].pt[adminMetadataPropertyLabel].userValue['http://id.loc.gov/ontologies/bibframe/adminMetadata'][0]["http://id.loc.gov/ontologies/bflc/catalogerId"]){ - useProfile.rt[rt].pt[adminMetadataPropertyLabel].userValue['http://id.loc.gov/ontologies/bibframe/adminMetadata'][0]["http://id.loc.gov/ontologies/bflc/catalogerId"] = [ - { - "@guid": short.generate(), - "http://id.loc.gov/ontologies/bflc/catalogerId": addAdmin - } - ] - } - // TODO remove local system number - }else{ - // console.log("Admin does not exists for ", rt, 'adding it') - // doesn't exist, add it to the ptOrder and pt - useProfile.rt[rt].ptOrder.push(adminMetadataPropertyLabel) - useProfile.rt[rt].pt[adminMetadataPropertyLabel] = JSON.parse(JSON.stringify(adminMetadataProperty)) - } - } - } + // make a lookup for just the profiles rts + let plookup = {} + for (let p of Object.keys(this.profiles)) { + this.profiles[p].rtOrder.forEach((rtname) => { + plookup[rtname] = this.profiles[p].rt[rtname] + }) + } + // we have starting points, generate those profiles based on their requirements + // this is just converting the format of the old BFE starting points format + if (Array.isArray(startingPointData)) { + startingPointData = startingPointData[0] + } + + + + // // removed march 2026 + // if (config.returnUrls.env != 'production'){ + // startingPointData.json.splice(2,0,{ + // "menuGroup": "GPO Monograph", + // "menuItems": [ + // { + // "label": "Instance", + // "type": [ + // "http://id.loc.gov/ontologies/bibframe/Instance" + // ], + // "useResourceTemplates": [ + // "lc:RT:bf2:GPOMono:Instance" + // ] + // }, + // { + // "label": "Work", + // "type": [ + // "http://id.loc.gov/ontologies/bibframe/Work" + // ], + // "useResourceTemplates": [ + // "lc:RT:bf2:GPOMono:Work" + // ] + // } + // ] + // }) + // startingPointData.json.splice(3,0,{ + // "menuGroup": "GPO Serial", + // "menuItems": [ + // { + // "label": "Instance", + // "type": [ + // "http://id.loc.gov/ontologies/bibframe/Instance" + // ], + // "useResourceTemplates": [ + // "lc:RT:bf2:GPOSerial:Instance" + // ] + // }, + // { + // "label": "Work", + // "type": [ + // "http://id.loc.gov/ontologies/bibframe/Work" + // ], + // "useResourceTemplates": [ + // "lc:RT:bf2:GPOSerial:Work" + // ] + // } + // ] + // }) + // } - // console.log(JSON.stringify(useProfile,null,2)) + startingPointData.json.forEach((sp) => { + this.startingPoints[sp.menuGroup] = { name: sp.menuGroup, work: null, instance: null, item: null } + sp.menuItems.forEach((mi) => { - return useProfile + if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Instance') > -1) { + this.startingPoints[sp.menuGroup].instance = mi.useResourceTemplates[0] + } + if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Work') > -1) { - }, + this.startingPoints[sp.menuGroup].work = mi.useResourceTemplates[0] + } + if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Item') > -1) { + this.startingPoints[sp.menuGroup].item = mi.useResourceTemplates[0] + } + if (mi.type.indexOf('http://id.loc.gov/ontologies/bibframe/Hub') > -1) { - /** - * Keeps track of what field and component the user interface is currently working in - * - * @param {object} structure - the profile structure the field is being built with - * @return {void} - */ - setActiveField: function(structure){ + this.startingPoints[sp.menuGroup].hub = mi.useResourceTemplates[0] + } + }) + // create a "profile" for this starting point in our profiles + this.profiles[sp.menuGroup] = { id: sp.menuGroup, rt: {}, rtOrder: [] } + if (this.startingPoints[sp.menuGroup].hub) { + this.profiles[sp.menuGroup].rt[this.startingPoints[sp.menuGroup].hub] = plookup[this.startingPoints[sp.menuGroup].hub] + this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].hub) + } + if (this.startingPoints[sp.menuGroup].work) { + this.profiles[sp.menuGroup].rt[this.startingPoints[sp.menuGroup].work] = plookup[this.startingPoints[sp.menuGroup].work] + this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].work) + } - }, + if (this.startingPoints[sp.menuGroup].instance) { + this.profiles[sp.menuGroup].rt[this.startingPoints[sp.menuGroup].instance] = plookup[this.startingPoints[sp.menuGroup].instance] + this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].instance) + } + // if there is a hub and work and instance then always put the hub at the start + if (this.startingPoints[sp.menuGroup].hub && this.startingPoints[sp.menuGroup].work && this.startingPoints[sp.menuGroup].instance) { - /** - * Prepares the data in the component for switch the ref template, for example going - * from main title to variant title, it needs to change the userValue mostly - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value - * @param {object} nextRef - the template object representing the template we are switching to - * @param {object} thisRef - the template object representing the template we currently on + this.profiles[sp.menuGroup].rtOrder = [] + this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].hub) + this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].work) + this.profiles[sp.menuGroup].rtOrder.push(this.startingPoints[sp.menuGroup].instance) + } - * @return {void} - */ - changeRefTemplate: function(componentGuid, propertyPath, nextRef, thisRef){ - // let lastProperty = propertyPath.at(-1).propertyURI - // // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - //should be safe to delete the cache when swaping templates - if (Object.keys(cachePt).includes(componentGuid)){ - delete cachePt[componentGuid] - } - for (let guid of Object.keys(cacheGuid)){ - cleanCacheGuid(cacheGuid, JSON.parse(JSON.stringify(pt.userValue)), guid) - } - if (pt !== false){ - pt.activeType = nextRef.resourceURI - let baseURI = pt.propertyURI - // map to the first level - if (!pt.userValue[baseURI]){ - pt.userValue[baseURI]=[{}] - } - let userValue = pt.userValue[baseURI][0] - // always remove the @id, except for subjects. Without it, things won't be linked after swapping - if (userValue['@id'] && !(thisRef.id.includes(":Components") || thisRef.id.includes("Topic"))){ - delete userValue['@id'] - } + }) - // keep PrimaryContribution - if (!["http://id.loc.gov/ontologies/bibframe/PrimaryContribution"].includes(userValue['@type'])){ - userValue['@type'] = nextRef.resourceURI - } + for (let p in this.profiles) { + this.profiles[p].hashRts = {} + this.profiles[p].hashPts = {} - // store the other properies as well - if (!pt.refTemplateUserValueKeys){ - pt.refTemplateUserValueKeys = {} - } + for (let rt in this.profiles[p].rt) { + if (this.profiles[p].rt[rt]) { + this.profiles[p].hashRts[rt] = this.hashRt(this.profiles[p].rt[rt]) + } - if (!pt.refTemplateUserValueKeys[thisRef.id]){ - pt.refTemplateUserValueKeys[thisRef.id] = [] - } + if (this.profiles[p].rt[rt] && this.profiles[p].rt[rt].pt) { + for (let pt in this.profiles[p].rt[rt].pt) { + // build the key to the property + let id = rt + '|' + this.profiles[p].rt[rt].pt[pt].propertyURI + if (this.profiles[p].rt[rt].pt[pt].valueConstraint && this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType && this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType.dataTypeURI && this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType.dataTypeURI.trim() != '') { + id = id + '|' + this.profiles[p].rt[rt].pt[pt].valueConstraint.valueDataType.dataTypeURI + } + // builds an id like this: lc:RT:bf2:35mmFeatureFilm:Work|http://id.loc.gov/ontologies/bibframe/contribution|http://id.loc.gov/ontologies/bflc/PrimaryContribution + let ptVal = JSON.parse(JSON.stringify(this.profiles[p].rt[rt].pt[pt])) + // ensure reliable order + ptVal.valueConstraint = Object.keys(ptVal.valueConstraint).sort().reduce( + (obj, key) => { + obj[key] = ptVal.valueConstraint[key]; + return obj; + }, + {} + ); + + delete ptVal['@guid'] + delete ptVal['hashCode'] + delete ptVal['hashCodeId'] + delete ptVal['userValue'] + delete ptVal['canBeHidden'] + delete ptVal['dataLoaded'] + delete ptVal['deepHierarchy'] + delete ptVal['hasData'] + delete ptVal['remark'] + delete ptVal['parent'] + delete ptVal['xmlHash'] + delete ptVal['xmlSource'] + delete ptVal['userModified'] + delete ptVal['activeType'] + delete ptVal['missingProfile'] + delete ptVal['valueConstraint']['editable'] + delete ptVal['valueConstraint']['repeatable'] + delete ptVal['valueConstraint']['defaults'] + + try { + delete ptVal['valueConstraint']['valueDataType']['dataTypeLabel'] + delete ptVal['valueConstraint']['valueDataType']['remark'] + } catch { } + + let orderedPtVal = Object.keys(ptVal).sort().reduce( + (obj, key) => { + obj[key] = ptVal[key]; + return obj; + }, + {} + ); + this.profiles[p].hashPts[id] = hashCode(JSON.stringify(orderedPtVal)) + this.profiles[p].rt[rt].pt[pt].hashCode = hashCode(JSON.stringify(orderedPtVal)) + this.profiles[p].rt[rt].pt[pt].hashCodeId = id + } - for (let key in pt.userValue){ - if (!key.startsWith('@')){ - pt.refTemplateUserValueKeys[thisRef.id].push(key) - } - } - - // if there are properties in the old template that are not in the new one then we need to remove them from the userValue - let possibleProperties = nextRef.propertyTemplates.map((p) => {return p.propertyURI}) - - // The subject properties need to be updated, or the data will be erased, is there a better way? - if (thisRef.id.includes(":Components") || thisRef.id.includes("Topic")){ - possibleProperties = ["@id", "http://www.loc.gov/mads/rdf/v1#componentList", "http://www.w3.org/2000/01/rdf-schema#label", "http://id.loc.gov/ontologies/bibframe/source", "http://id.loc.gov/ontologies/bflc/marcKey", "http://www.loc.gov/mads/rdf/v1#authoritativeLabel", "http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] - } - if (!pt.refTemplateUserValue){ - pt.refTemplateUserValue = {} - } + } + } + } + // console.log('this.profiles',this.profiles) + this.profilesLoaded = true + // return { profiles: this.profiles, lookup: this.rtLookup, startingPoints: this.startingPoints} - for (let key in userValue){ - //For contributions, keep the keys for agent role so the data persists - if (!key.startsWith('@') && !["http://id.loc.gov/ontologies/bibframe/agent", "http://id.loc.gov/ontologies/bibframe/role"].includes(key)){ - if (possibleProperties.indexOf(key) == -1){ - // this property has no place in the ref template we are about to switch to - // so store them over in the refTemplateUserValue for later if needed - pt.refTemplateUserValue[key] = JSON.parse(JSON.stringify(userValue[key])) - delete userValue[key] - } - } else if (key == "@id"){ //for subjects, keep the "@id" - pt.refTemplateUserValue[key] = JSON.parse(JSON.stringify(userValue[key])) - delete userValue[key] - } - } - // see if there are any properties stored in refTemplateUserValue that - // can be filled into this template + }, - for (let pp of possibleProperties){ - if (pt.refTemplateUserValue[pp]){ - // don't use http://id.loc.gov/ontologies/bibframe/assigner aka source - // kind of a hackish thing, but the source is really not transferable between - // differnt types of classifications so leave it out, it will get populated with the default so - // we shouldn't loose any data, only if they change it then cycle the options then it will be lost and need to re-add - if (pp != 'http://id.loc.gov/ontologies/bibframe/assigner'){ - userValue[pp]= JSON.parse(JSON.stringify(pt.refTemplateUserValue[pp])) - } - delete pt.refTemplateUserValue[pp] + /** + * Takes a resource template from the profile and creates a hash of the json-fied version of it to use as a fingerprint, to see if it changes later + * For use in "template" functionality, to know if a resource template has changed since a template has used it + * + * @param {object} rt - the rt from the profile + * @return {number} The fingerprint hash + */ + hashRt: function (rt) { + rt = JSON.parse(JSON.stringify(rt)) + for (let pt in rt.pt) { + delete rt.pt[pt]['@guid'] } - } - - // also check to see if there are default values in the orignal profile that we might need to over write with if they are switching + return hashCode(JSON.stringify(rt)) + }, - // for (let ptIdx of this.rtLookup[nextRef.id].propertyTemplates){ - // if (ptIdx.valueConstraint.defaults && ptIdx.valueConstraint.defaults.length>0){ - // // console.log("These fdautls:",ptIdx.valueConstraint.defaults && ptIdx.valueConstraint.defaults[0]) - // // console.log(ptIdx.propertyURI) - // // if there is already this property in the uservalue remove it - // if (userValue[ptIdx.propertyURI]){ - // userValue[ptIdx.propertyURI] = [] - // } - // // popualte with the default - // if (ptIdx.valueConstraint.defaults[0].defaultLiteral){ + /** + * does all the work to setup a new profile read to be edited and posted as new resource + * + * @param {string} useStartingPoint - the name of the starting point to use to as the profile + * @param {string} addAdmin - the caloger user id to use in the admin metadata, if undefined it will not add a adminMetadata + * @param {object} userTemplateSupplied - a specific profile is being asked to be prepared, so don't look it up in the starting points, just use this one + * @return {object} useProfile - the profile modified ready to bet set in the state and edited + */ + loadNewTemplate(useStartingPoint, addAdmin, userTemplateSupplied) { + const config = useConfigStore() + // should be the catinitals to insert into admin if being passed + if (typeof addAdmin === 'undefined') { + addAdmin = false + } - // // if the default is for a label property, don't double nest it - // if (ptIdx.propertyURI === 'http://www.w3.org/2000/01/rdf-schema#label'){ + let useProfile - // userValue[ptIdx.propertyURI]= [ - // { - // 'http://www.w3.org/2000/01/rdf-schema#label':ptIdx.valueConstraint.defaults[0].defaultLiteral, - // '@guid': short.generate(), - // } - // ] + if (userTemplateSupplied) { + useProfile = userTemplateSupplied + } else { + useProfile = JSON.parse(JSON.stringify(this.profiles[useStartingPoint])) + } - // }else{ - // userValue[ptIdx.propertyURI]= [{ - // '@guid': short.generate(), - // 'http://www.w3.org/2000/01/rdf-schema#label': [ - // { - // 'http://www.w3.org/2000/01/rdf-schema#label':ptIdx.valueConstraint.defaults[0].defaultLiteral, - // '@guid': short.generate(), - // } - // ] + // some profiles have nested components at the root level used in that component + // so if it doesn't end with one of the main type of resources we want to edit + // then we don't want to render it, since it is probably being used in the main RT somewhere + let toRemove = [] + let toKeep = [] + for (let rt of useProfile.rtOrder) { - // }] + let hasTLS = false + for (let TLS of config.validTopLevelProfileSufixes) { + if (rt.includes(TLS)) { + hasTLS = true + } + } - // } + if (hasTLS === false) { + toRemove.push(rt) + } else { + toKeep.push(rt) + } + } + for (let rt of toRemove) { + delete useProfile.rt[rt] + } + useProfile.rtOrder = toKeep - // } - // if (ptIdx.valueConstraint.defaults[0].defaultURI && ptIdx.valueConstraint.defaults[0].defaultURI.trim() != ""){ + if (!useProfile.log) { + useProfile.log = [] + } + useProfile.log.push({ action: 'createWorkInstance' }) + useProfile.procInfo = config.procInfoNewWorkInstance - // userValue[ptIdx.propertyURI][0]['@id'] = ptIdx.valueConstraint.defaults[0].defaultURI + // also give it an ID for storage + if (!useProfile.eId) { + // let uuid = 'e' + decimalTranslator.new() + // uuid = uuid.substring(0,8) + let uuid = 'e' + Date.now().toString() + useProfile.eId = uuid - // if (ptIdx.valueConstraint.valueDataType && ptIdx.valueConstraint.valueDataType.dataTypeURI){ - // userValue[ptIdx.propertyURI][0]['@type'] = ptIdx.valueConstraint.valueDataType.dataTypeURI - // } + } + if (!useProfile.user) { + useProfile.user = usePreferenceStore().returnUserNameForSaving + } + if (!useProfile.status) { + useProfile.status = 'unposted' + } - // } + let workUri = null + let workUriId = translator.toUUID(translator.new()) + for (let rt in useProfile.rt) { + let uri = null + // make a new uri for each one + if (rt.endsWith(config.validTopLevelWork)) { + uri = config.baseURIWork + workUriId + workUri = uri + } else if (rt.endsWith(config.validTopLevelInstance)) { + // when making a new instance from scratch use the work URI Id peice as the instance ID piece + uri = config.baseURIInstance + workUriId - // } - // } + } else if (rt.endsWith(config.validTopLevelItem)) { + uri = config.baseURIItem + translator.toUUID(translator.new()) + } else if (rt.endsWith(config.validTopLevelHub)) { + uri = config.baseURIHub + translator.toUUID(translator.new()) + } else { + // dunno what this is give it a random uri + uri = 'http://id.loc.gov/resources/unknown/' + translator.toUUID(translator.new()) + } - // they changed something - this.dataChanged() - }else{ - console.error('changeRefTemplate: Cannot locate the component by guid', componentGuid, this.activeProfile) - } + useProfile.rt[rt].URI = uri + for (let pt in useProfile.rt[rt].pt) { - }, + if (useProfile.rt[rt].pt[pt].propertyURI == "http://id.loc.gov/ontologies/bibframe/Work") { + useProfile.rt[rt].pt[pt].userValue = { + '@root': 'http://id.loc.gov/ontologies/bibframe/Work', + '@guid': short.generate(), + '@id': uri, + } + } + } + } + // apply the data we gathered / created above + for (let rt in useProfile.rt) { + if (rt.includes(config.validTopLevelWork)) { + // something + } else if (rt.includes(config.validTopLevelInstance)) { + //something + useProfile.rt[rt].instanceOf = workUri + } else if (rt.includes(config.validTopLevelItem)) { + //something + } + } - /** - * Sets a "Simple lookup" value, things from small controlled lists like role - * this function only creates new values, does not modify (aka delete) - * @async - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} fieldGuid - the guid of the field - * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value - * @param {string} URI - the URI for the value - * @param {string} label - the label to use - - * @return {void} - */ - setValueSimple: async function(componentGuid, fieldGuid, propertyPath, URI, label){ - console.log("componentGuid, fieldGuid, propertyPath, URI, label") - console.log(componentGuid, fieldGuid, propertyPath, URI, label) - propertyPath = JSON.parse(JSON.stringify(propertyPath)) - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.w3.org/2002/07/owl#sameAs') }) - - // if there's a code in the label, take it out - if (!URI){ URI = '' } - const code = URI.split("/").at(-1) // is this reliable? - if (label.match("(" + code + ")")){ - label = label.replace("(" + code + ")", "") - } - - let lastProperty = propertyPath.at(-1).propertyURI - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - if (pt !== false){ - - pt.hasData = true - pt.userModified = true - pt.dataLoaded = false - - // find the correct blank node to edit if possible, if we don't find it then we need to create it - let blankNode = utilsProfile.returnGuidLocation(pt.userValue,fieldGuid) - - if (blankNode === false){ - // create the path to the blank node - let buildBlankNodeResult = await utilsProfile.buildBlanknode(pt,propertyPath) - - pt = buildBlankNodeResult[0] - - // now we can make a link to the parent of where the literal value should live - blankNode = utilsProfile.returnGuidLocation(pt.userValue,buildBlankNodeResult[1]) - - // set the URI - // if its null then we are adding a literal - if (URI !== null){ - blankNode['@id'] = URI - }else{ - // do nothing for now... - } - - blankNode['http://www.w3.org/2000/01/rdf-schema#label'] = [ - { - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : label - } - ] - }else{ - - let parent = utilsProfile.returnGuidParent(pt.userValue,fieldGuid) - - // make sure we can find where to put the new one - if (parent[lastProperty]){ - // create a new node here - let toadd = { - '@id': URI, - '@guid' : short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : [ - { - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : label - } - ] - } - let type = utilsRDF.suggestTypeProfile(lastProperty,pt) - if (type === false){ - // did not find it in the profile, look to the network - type = await utilsRDF.suggestTypeNetwork(lastProperty) - } - if (type !== false){ - // first we test to see if the type is a literal, if so then we - // don't need to set the type, as its not a blank node, just a nested property - if (utilsRDF.isUriALiteral(type) === false){ - // if it doesn't yet have a type then go ahead and set it - toadd['@type'] = type - }else{ - // nothing to do, its a literal - } - } - parent[lastProperty].push(toadd) - }else if (propertyPath.length==1){ - - // this is a top level component, like bf:content - // there will be no lastProperty, just add it to the parent directly - let toadd = { - '@id': URI, - '@guid' : short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : [ - { - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : label - } - ] - } - let type = utilsRDF.suggestTypeProfile(lastProperty,pt) - if (type === false){ - // did not find it in the profile, look to the network - type = await utilsRDF.suggestTypeNetwork(lastProperty) - } - if (type !== false){ - // first we test to see if the type is a literal, if so then we - // don't need to set the type, as its not a blank node, just a nested property - if (utilsRDF.isUriALiteral(type) === false){ - // if it doesn't yet have a type then go ahead and set it - toadd['@type'] = type - }else{ - // nothing to do, its a literal - } - } - parent.push(toadd) - }else{ - console.log("lastProperty",lastProperty) - console.log('propertyPath',propertyPath) - - console.error("Could not find the parent[lastProperty] of the existing value", {'parent':parent,'pt.userValue':pt.userValue, 'fieldGuid':fieldGuid}) - } + // console.log('------useProfile-------') + // console.log(useProfile) + if (addAdmin) { + for (let rt in useProfile.rt) { + let adminMetadataProperty = { + "mandatory": false, + "propertyLabel": "Admin Metadata", + "propertyURI": "http://id.loc.gov/ontologies/bibframe/adminMetadata", + "repeatable": false, + "resourceTemplates": [], + '@guid': short.generate(), + "type": "resource", + "userValue": { + "@root": "http://id.loc.gov/ontologies/bibframe/adminMetadata", + "http://id.loc.gov/ontologies/bibframe/adminMetadata": [{ + + "@type": "http://id.loc.gov/ontologies/bibframe/AdminMetadata", + '@guid': short.generate(), + "http://id.loc.gov/ontologies/bflc/catalogerId": [ + { + "@guid": short.generate(), + "http://id.loc.gov/ontologies/bflc/catalogerId": addAdmin + } + ] + }] + }, + "valueConstraint": { + "defaults": [], + "useValuesFrom": [], + "valueDataType": {}, + "valueTemplateRefs": [(!rt.includes(':GPO')) ? 'lc:RT:bf2:AdminMetadata:BFDB' : 'lc:RT:bf2:GPOMono:AdminMetadata'] + } + } + let adminMetadataPropertyLabel = 'http://id.loc.gov/ontologies/bibframe/adminMetadata'.replace('http://', '').replace('https://', '').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "_") + '__admin_metadata' - } + if (useProfile.rt[rt].pt[adminMetadataPropertyLabel]) { + // console.log("Admin already exists for ", rt, 'chahhing userid') + // it already exists, so update the catalogerId and use the existing userValue - // they changed something - this.dataChanged() + if (useProfile.rt[rt].pt[adminMetadataPropertyLabel].userValue['http://id.loc.gov/ontologies/bibframe/adminMetadata'][0]["http://id.loc.gov/ontologies/bflc/catalogerId"]) { + useProfile.rt[rt].pt[adminMetadataPropertyLabel].userValue['http://id.loc.gov/ontologies/bibframe/adminMetadata'][0]["http://id.loc.gov/ontologies/bflc/catalogerId"] = [ + { + "@guid": short.generate(), + "http://id.loc.gov/ontologies/bflc/catalogerId": addAdmin + } + ] + } + // TODO remove local system number - }else{ - console.error('setValueSimple: Cannot locate the component by guid', componentGuid, this.activeProfile) - } - }, + } else { + // console.log("Admin does not exists for ", rt, 'adding it') + // doesn't exist, add it to the ptOrder and pt + useProfile.rt[rt].ptOrder.push(adminMetadataPropertyLabel) + useProfile.rt[rt].pt[adminMetadataPropertyLabel] = JSON.parse(JSON.stringify(adminMetadataProperty)) + } + } + } - /** - * This removes the values of a complex lookup field - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} fieldGuid - the guid of the field - * @return {void} - */ - removeValueComplex: async function(componentGuid, fieldGuid){ - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) + // console.log(JSON.stringify(useProfile,null,2)) - if (pt !== false){ - // find the correct blank node to edit if possible, + return useProfile - let parent = utilsProfile.returnGuidParent(pt.userValue,fieldGuid) - if (parent && parent.length === 1 && parent[0]['@guid'] === fieldGuid ){ - // console.log("The parent is the node we are looking for") - parent = pt.userValue - } - // just look through all of the properties, if its an array filter it - for (let p in parent){ - if (Array.isArray(parent[p])){ - parent[p] = parent[p].filter((v) => { + }, - if (v && v['@guid'] && v['@guid'] === fieldGuid){ - return false - }else{ - return true - } - }) - } - } - // check to make sure that we didn't make an empty property - // remove the property key if so - for (let p in parent){ - if (Array.isArray(parent[p])){ - if (parent[p].length===0){ - delete parent[p] - } - } - } + /** + * Keeps track of what field and component the user interface is currently working in + * + * @param {object} structure - the profile structure the field is being built with + * @return {void} + */ + setActiveField: function (structure) { - // console.log("post filter:",parent) - // console.log("The PT",pt) - // make sure we don't leave any blank blank nodes behind - pt.userValue = utilsProfile.pruneUserValue(pt.userValue) - // they changed something - this.dataChanged() + }, - }else{ - console.error('removeValueSimple: Cannot locate the component by guid', componentGuid, this.activeProfile) - } - }, + /** + * Prepares the data in the component for switch the ref template, for example going + * from main title to variant title, it needs to change the userValue mostly + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value + * @param {object} nextRef - the template object representing the template we are switching to + * @param {object} thisRef - the template object representing the template we currently on + * @return {void} + */ + changeRefTemplate: function (componentGuid, propertyPath, nextRef, thisRef) { + // let lastProperty = propertyPath.at(-1).propertyURI + // // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + //should be safe to delete the cache when swaping templates + if (Object.keys(cachePt).includes(componentGuid)) { + delete cachePt[componentGuid] + } + for (let guid of Object.keys(cacheGuid)) { + cleanCacheGuid(cacheGuid, JSON.parse(JSON.stringify(pt.userValue)), guid) + } - /** - * This removes the values of a simple lookup field - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} fieldGuid - the guid of the field - * @return {void} - */ - removeValueSimple: async function(componentGuid, fieldGuid){ - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) + if (pt !== false) { + pt.activeType = nextRef.resourceURI + let baseURI = pt.propertyURI - if (pt !== false){ + // map to the first level + if (!pt.userValue[baseURI]) { + pt.userValue[baseURI] = [{}] + } + let userValue = pt.userValue[baseURI][0] + // always remove the @id, except for subjects. Without it, things won't be linked after swapping + if (userValue['@id'] && !(thisRef.id.includes(":Components") || thisRef.id.includes("Topic"))) { + delete userValue['@id'] + } + // keep PrimaryContribution + if (!["http://id.loc.gov/ontologies/bibframe/PrimaryContribution"].includes(userValue['@type'])) { + userValue['@type'] = nextRef.resourceURI + } - // is it a top level property, meaning there is no need to dig through the blank nodes of the userValue - if (pt.valueConstraint.valueTemplateRefs.length === 0){ + // store the other properies as well + if (!pt.refTemplateUserValueKeys) { + pt.refTemplateUserValueKeys = {} + } - // filter the entire uservalue - for (let key in pt.userValue){ + if (!pt.refTemplateUserValueKeys[thisRef.id]) { + pt.refTemplateUserValueKeys[thisRef.id] = [] + } - if (Array.isArray(pt.userValue[key])){ - pt.userValue[key] = pt.userValue[key].filter((v) => { - if (v && v['@guid'] && v['@guid'] === fieldGuid){ - return false - }else{ - return true + for (let key in pt.userValue) { + if (!key.startsWith('@')) { + pt.refTemplateUserValueKeys[thisRef.id].push(key) + } } - }) - } - } - for (let key in pt.userValue){ - if (Array.isArray(pt.userValue[key])){ - if (pt.userValue[key].length===0){ - delete pt.userValue[key] - } - } - } - this.dataChanged() - return true + // if there are properties in the old template that are not in the new one then we need to remove them from the userValue + let possibleProperties = nextRef.propertyTemplates.map((p) => { return p.propertyURI }) - } + // The subject properties need to be updated, or the data will be erased, is there a better way? + if (thisRef.id.includes(":Components") || thisRef.id.includes("Topic")) { + possibleProperties = ["@id", "http://www.loc.gov/mads/rdf/v1#componentList", "http://www.w3.org/2000/01/rdf-schema#label", "http://id.loc.gov/ontologies/bibframe/source", "http://id.loc.gov/ontologies/bflc/marcKey", "http://www.loc.gov/mads/rdf/v1#authoritativeLabel", "http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] + } + if (!pt.refTemplateUserValue) { + pt.refTemplateUserValue = {} + } + for (let key in userValue) { + //For contributions, keep the keys for agent role so the data persists + if (!key.startsWith('@') && !["http://id.loc.gov/ontologies/bibframe/agent", "http://id.loc.gov/ontologies/bibframe/role"].includes(key)) { + if (possibleProperties.indexOf(key) == -1) { + // this property has no place in the ref template we are about to switch to + // so store them over in the refTemplateUserValue for later if needed + pt.refTemplateUserValue[key] = JSON.parse(JSON.stringify(userValue[key])) + delete userValue[key] + } + } else if (key == "@id") { //for subjects, keep the "@id" + pt.refTemplateUserValue[key] = JSON.parse(JSON.stringify(userValue[key])) + delete userValue[key] + } + } - // find the correct blank node to edit if possible, if we don't find it then we need to create it - // console.log(pt) - // console.log("fieldGuid",fieldGuid) + // see if there are any properties stored in refTemplateUserValue that + // can be filled into this template + + for (let pp of possibleProperties) { + if (pt.refTemplateUserValue[pp]) { + // don't use http://id.loc.gov/ontologies/bibframe/assigner aka source + // kind of a hackish thing, but the source is really not transferable between + // differnt types of classifications so leave it out, it will get populated with the default so + // we shouldn't loose any data, only if they change it then cycle the options then it will be lost and need to re-add + if (pp != 'http://id.loc.gov/ontologies/bibframe/assigner') { + userValue[pp] = JSON.parse(JSON.stringify(pt.refTemplateUserValue[pp])) + } + delete pt.refTemplateUserValue[pp] + } + } - let parent = utilsProfile.returnGuidParent(pt.userValue,fieldGuid) + // also check to see if there are default values in the orignal profile that we might need to over write with if they are switching - // just look through all of the properties, if its an array filter it - for (let p in parent){ - if (Array.isArray(parent[p])){ - parent[p] = parent[p].filter((v) => { - if (v && v['@guid'] && v['@guid'] === fieldGuid){ - return false - }else{ - return true - } - }) - } if (['object'].includes(typeof parent[p]) && parent[p] !== null){ - - // check the parent if there are only two keys this is a top level - // simple lookup, blank out the non-root key and that should clear this value - // if (Object.keys(pt.userValue).length==2){ - // for (let k in pt.userValue){ - // if (k != '@root'){ - // pt.userValue[k] = [] - // } - // } - // } + // for (let ptIdx of this.rtLookup[nextRef.id].propertyTemplates){ + // if (ptIdx.valueConstraint.defaults && ptIdx.valueConstraint.defaults.length>0){ + // // console.log("These fdautls:",ptIdx.valueConstraint.defaults && ptIdx.valueConstraint.defaults[0]) + // // console.log(ptIdx.propertyURI) + // // if there is already this property in the uservalue remove it + // if (userValue[ptIdx.propertyURI]){ + // userValue[ptIdx.propertyURI] = [] + // } - // // not an array just remove the values - // for (let key in parent[p]){ - // if (key !='@guid'){ - // delete parent[p][key] - // } - // } + // // popualte with the default + // if (ptIdx.valueConstraint.defaults[0].defaultLiteral){ - } - } + // // if the default is for a label property, don't double nest it + // if (ptIdx.propertyURI === 'http://www.w3.org/2000/01/rdf-schema#label'){ - // check to make sure that we didn't make an empty property - // remove the property key if so - for (let p in parent){ - if (Array.isArray(parent[p])){ - if (parent[p].length===0){ - delete parent[p] - } - } - } + // userValue[ptIdx.propertyURI]= [ + // { + // 'http://www.w3.org/2000/01/rdf-schema#label':ptIdx.valueConstraint.defaults[0].defaultLiteral, + // '@guid': short.generate(), + // } + // ] - // see if we removed it from those actions - parent = utilsProfile.returnGuidParent(pt.userValue,fieldGuid) + // }else{ + // userValue[ptIdx.propertyURI]= [{ + // '@guid': short.generate(), + // 'http://www.w3.org/2000/01/rdf-schema#label': [ + // { + // 'http://www.w3.org/2000/01/rdf-schema#label':ptIdx.valueConstraint.defaults[0].defaultLiteral, + // '@guid': short.generate(), + // } + // ] + // }] - if (parent !== false){ + // } - for (let p in parent){ - if (['object'].includes(typeof parent[p]) && parent[p] !== null){ - // check the parent if there are only two keys this is a top level - // simple lookup, blank out the non-root key and that should clear this value - if (Object.keys(pt.userValue).length==2){ - // if they have a @type keep it around for later so it remains that type when the clear the value - // so they dont need to change interface back to that reference component - let oldType = null - let oldTypeParent = null + // } - for (let k in pt.userValue){ - if (pt.userValue[k] && pt.userValue[k][0] && pt.userValue[k][0]['@type']){ - oldType = pt.userValue[k][0]['@type'] - oldTypeParent = k - } - } + // if (ptIdx.valueConstraint.defaults[0].defaultURI && ptIdx.valueConstraint.defaults[0].defaultURI.trim() != ""){ - for (let k in pt.userValue){ - if (k != '@root'){ - delete pt.userValue[k] - } - } - // set the old type back so it doesn't change the reference select in the user interface - if (oldType && oldTypeParent){ - pt.userValue[oldTypeParent] = [ - { - '@type': oldType - } - ] + // userValue[ptIdx.propertyURI][0]['@id'] = ptIdx.valueConstraint.defaults[0].defaultURI - } + // if (ptIdx.valueConstraint.valueDataType && ptIdx.valueConstraint.valueDataType.dataTypeURI){ + // userValue[ptIdx.propertyURI][0]['@type'] = ptIdx.valueConstraint.valueDataType.dataTypeURI + // } - } - // // not an array just remove the values - // for (let key in parent[p]){ - // if (key !='@guid'){ - // delete parent[p][key] - // } - // } + // } - } - } - } - // they changed something - this.dataChanged() + // } + // } - // console.log("psot filter em:",parent) + // they changed something + this.dataChanged() + } else { + console.error('changeRefTemplate: Cannot locate the component by guid', componentGuid, this.activeProfile) + } - // if (blankNode === false){ + }, - // }else{ + /** + * Sets a "Simple lookup" value, things from small controlled lists like role + * this function only creates new values, does not modify (aka delete) + * @async + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} fieldGuid - the guid of the field + * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value + * @param {string} URI - the URI for the value + * @param {string} label - the label to use - // } - }else{ - console.error('removeValueSimple: Cannot locate the component by guid', componentGuid, this.activeProfile) - } + * @return {void} + */ + setValueSimple: async function (componentGuid, fieldGuid, propertyPath, URI, label) { + console.log("componentGuid, fieldGuid, propertyPath, URI, label") + console.log(componentGuid, fieldGuid, propertyPath, URI, label) + propertyPath = JSON.parse(JSON.stringify(propertyPath)) + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.w3.org/2002/07/owl#sameAs') }) + // if there's a code in the label, take it out + if (!URI) { URI = '' } + const code = URI.split("/").at(-1) // is this reliable? + if (label.match("(" + code + ")")) { + label = label.replace("(" + code + ")", "") + } - }, + let lastProperty = propertyPath.at(-1).propertyURI + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + if (pt !== false) { + pt.hasData = true + pt.userModified = true + pt.dataLoaded = false + // find the correct blank node to edit if possible, if we don't find it then we need to create it + let blankNode = utilsProfile.returnGuidLocation(pt.userValue, fieldGuid) - /** - * Sets a literal value of field - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} fieldGuid - the guid of the field - * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value - * @param {string} lang - the ISO rdf language value like 'en' to append to the literal 'xxxxx@en' - * @return {void} - */ - setValueLiteral: function(componentGuid, fieldGuid, propertyPath, value, lang, repeatedLiteral){ - // console.info("--------------------------\nsetValueLiteral") - // console.info("\tcomponentGuid: ", componentGuid) - // console.info("\tfieldGuid: ", fieldGuid) - // console.info("\tpropertyPath: ", propertyPath) - // console.info("\tvalue: ", value) - // console.info("\tlang: ", lang) - // componentGuid: aiPuH4YsetZ9xmcv7rqisJ - // fieldGuid: pdtUXGpNDJ9mz33JM3uxje - - // from NAR, fieldGuid is null - - // remove returns from value - value = value.replace(/[\n\r]+/g, ''); - - // make a copy of the property path, dont modify the linked one passed - propertyPath = JSON.parse(JSON.stringify(propertyPath)) - - - //The propertyPath for supplementaryContent's note is missing the note. It jumps straight to the label - // so insert it so XML can get built - if (propertyPath.some((pp) => pp.propertyURI.includes("supplementaryContent")) && propertyPath.at(-1).propertyURI == "http://www.w3.org/2000/01/rdf-schema#label"){ - propertyPath.splice(1, 0, { level: 1, propertyURI: "http://id.loc.gov/ontologies/bibframe/note" }) - propertyPath.at(-1).level = 2 - } - - // this needs to include a check for "supplementaryContent", so the note will populate in the form - let isLocator = propertyPath.some((pp) => pp.propertyURI.includes("electronicLocator") || pp.propertyURI.includes("supplementaryContent")) - - // for the electronic locator, the path ends with `sameAs`, but it just gets in the way, toss it - if (isLocator){ - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.w3.org/2002/07/owl#sameAs') }) - } - - let lastProperty = propertyPath.at(-1).propertyURI - // locate the correct pt to work on in the activeProfile - let pt - if (cachePt[componentGuid]){ - pt = cachePt[componentGuid] - }else{ - pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - cachePt[componentGuid] = pt - } - - // used later to sort the literal order if needed - let checkLiteralOrder = null - - //clear the cache if the value was deleted - if (value.trim() == ""){ - if (Object.keys(cachePt).includes(componentGuid)){ - delete cachePt[componentGuid] - } - for (let guid of Object.keys(cacheGuid)){ - cleanCacheGuid(cacheGuid, JSON.parse(JSON.stringify(pt.userValue)), guid) - } - } - - // console.log("--------pt 1------------") - // console.log(JSON.stringify(pt,null,2)) - // let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - // console.log(componentGuid, fieldGuid, propertyPath, value, lang, repeatedLiteral) - if (pt !== false){ - pt.hasData = true - pt.userModified = true - pt.dataLoaded = false - - // find the correct blank node to edit if possible, if we don't find it then we need to create it - let blankNode - if (cacheGuid[fieldGuid]){ - blankNode = cacheGuid[fieldGuid] - }else{ - blankNode = utilsProfile.returnGuidLocation(pt.userValue,fieldGuid) - cacheGuid[fieldGuid] = blankNode - } + if (blankNode === false) { + // create the path to the blank node + let buildBlankNodeResult = await utilsProfile.buildBlanknode(pt, propertyPath) + pt = buildBlankNodeResult[0] - // console.log("--------pt 2------------") - // console.log(JSON.stringify(pt,null,2)) + // now we can make a link to the parent of where the literal value should live + blankNode = utilsProfile.returnGuidLocation(pt.userValue, buildBlankNodeResult[1]) - // let blankNode = utilsProfile.returnGuidLocation(pt.userValue,fieldGuid) + // set the URI + // if its null then we are adding a literal + if (URI !== null) { + blankNode['@id'] = URI + } else { + // do nothing for now... + } - if (blankNode === false){ - // create the path to the blank node - let buildBlankNodeResult + blankNode['http://www.w3.org/2000/01/rdf-schema#label'] = [ + { + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': label + } + ] + } else { - let currentValueCount = utilsProfile.countValues(pt,propertyPath) + let parent = utilsProfile.returnGuidParent(pt.userValue, fieldGuid) - if (currentValueCount === 0){ - // this is the first value, we need to construct the hierarchy to the bnode - buildBlankNodeResult = utilsProfile.buildBlanknode(pt,propertyPath, true) + // make sure we can find where to put the new one + if (parent[lastProperty]) { + // create a new node here + let toadd = { + '@id': URI, + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': [ + { + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': label + } + ] + } + let type = utilsRDF.suggestTypeProfile(lastProperty, pt) + if (type === false) { + // did not find it in the profile, look to the network + type = await utilsRDF.suggestTypeNetwork(lastProperty) + } + if (type !== false) { + // first we test to see if the type is a literal, if so then we + // don't need to set the type, as its not a blank node, just a nested property + if (utilsRDF.isUriALiteral(type) === false) { + // if it doesn't yet have a type then go ahead and set it + toadd['@type'] = type + } else { + // nothing to do, its a literal + } + } + parent[lastProperty].push(toadd) + } else if (propertyPath.length == 1) { + + // this is a top level component, like bf:content + // there will be no lastProperty, just add it to the parent directly + let toadd = { + '@id': URI, + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': [ + { + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': label + } + ] + } + let type = utilsRDF.suggestTypeProfile(lastProperty, pt) + if (type === false) { + // did not find it in the profile, look to the network + type = await utilsRDF.suggestTypeNetwork(lastProperty) + } + if (type !== false) { + // first we test to see if the type is a literal, if so then we + // don't need to set the type, as its not a blank node, just a nested property + if (utilsRDF.isUriALiteral(type) === false) { + // if it doesn't yet have a type then go ahead and set it + toadd['@type'] = type + } else { + // nothing to do, its a literal + } + } + parent.push(toadd) + } else { + console.log("lastProperty", lastProperty) + console.log('propertyPath', propertyPath) - // console.log("buildBlankNodeResult",JSON.stringify(buildBlankNodeResult,null,2)) - pt = buildBlankNodeResult[0] + console.error("Could not find the parent[lastProperty] of the existing value", { 'parent': parent, 'pt.userValue': pt.userValue, 'fieldGuid': fieldGuid }) + } - // now we can make a link to the parent of where the literal value should live - blankNode = utilsProfile.returnGuidLocation(pt.userValue,buildBlankNodeResult[1]) - // console.log("blankNode",JSON.stringify(blankNode,null,2)) - // this is a new node, so we want to overwrite the guid created in the build process - // with the one that was already created in the userinterface - blankNode['@guid'] = fieldGuid - // set a temp value that will be over written below - blankNode[lastProperty] = true - // console.log("--------pt 3------------") - // console.log(JSON.stringify(pt,null,2)) + } - }else{ - // there is already values here, so we need to insert a new value into the hiearchy + // they changed something + this.dataChanged() - let parent = utilsProfile.returnPropertyPathParent(pt,propertyPath) - if (!parent){ - console.error("Trying to add second literal, could not find the property path parent", pt) - return false + } else { + console.error('setValueSimple: Cannot locate the component by guid', componentGuid, this.activeProfile) } + }, - if (!parent[lastProperty]){ - console.error('Trying to find the value of this literal, unable to:',componentGuid, fieldGuid, propertyPath, value, lang, pt) - return false - } - let newGuid = short.generate() - // make a place for it - parent[lastProperty].push( - { - '@guid': newGuid, - } - ) - checkLiteralOrder = parent[lastProperty] + /** + * This removes the values of a complex lookup field + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} fieldGuid - the guid of the field + * @return {void} + */ + removeValueComplex: async function (componentGuid, fieldGuid) { - // get a link to it we'll edit it below - blankNode = utilsProfile.returnGuidLocation(pt.userValue,newGuid) - // set a temp value that will be over written below - blankNode[lastProperty] = true - // console.log("--------pt 4------------") - // console.log(JSON.stringify(pt,null,2)) - } - - // They used "Additional literal" on an empty field, add the new field - // without this, the user needs to run the action twice to the the additional field - if (currentValueCount === 0 && value=='new value'){ - // there is already values here, so we need to insert a new value into the hiearchy - - let parent = utilsProfile.returnPropertyPathParent(pt,propertyPath) - - if (!parent){ - console.error("Trying to add second literal, could not find the property path parent", pt) - return false - } - - if (!parent[lastProperty]){ - console.error('Trying to find the value of this literal, unable to:',componentGuid, fieldGuid, propertyPath, value, lang, pt) - return false - } - let newGuid = short.generate() - - // make a place for it - parent[lastProperty].push( - { - '@guid': newGuid, - } - ) - checkLiteralOrder = parent[lastProperty] - // get a link to it we'll edit it below - blankNode = utilsProfile.returnGuidLocation(pt.userValue,newGuid) - // set a temp value that will be over written below - blankNode[lastProperty] = true - } - // console.log("currentValueCount",currentValueCount) - } - if (!blankNode[lastProperty]){ - console.error('Trying to find the value of this literal, unable to:',componentGuid, fieldGuid, propertyPath, value, lang, pt) - } - if (lang == "REMOVE_COMMAND"){ - delete blankNode['@language'] - lang=null - } - if (lang){ - blankNode['@language'] = lang - } + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - // and now add in the literal value into the correct property - blankNode[lastProperty] = value + if (pt !== false) { - // for electronicLocators, update the ID, so the XML can get built correctly - if (isLocator && Object.keys(blankNode).some((key) => key == "http://id.loc.gov/ontologies/bibframe/electronicLocator")){ - blankNode["@id"] = value - } + // find the correct blank node to edit if possible, - // if we just set an empty value, remove the value property, and if there are no other values, remvoe the entire property - if (value.trim() === ''){ - delete blankNode[lastProperty] + let parent = utilsProfile.returnGuidParent(pt.userValue, fieldGuid) + if (parent && parent.length === 1 && parent[0]['@guid'] === fieldGuid) { + // console.log("The parent is the node we are looking for") + parent = pt.userValue + } - let parent = utilsProfile.returnPropertyPathParent(pt,propertyPath) + // just look through all of the properties, if its an array filter it + for (let p in parent) { + if (Array.isArray(parent[p])) { + parent[p] = parent[p].filter((v) => { - if (parent && parent[lastProperty]){ - let keep = [] - if (parent[lastProperty].length>0){ - for (let value of parent[lastProperty]){ - // does it have a value? - if (value[lastProperty] && value[lastProperty] != ''){ - keep.push(value) - }else{ - // console.log("dumping",value) + if (v && v['@guid'] && v['@guid'] === fieldGuid) { + return false + } else { + return true + } + }) + } } - } - } - parent[lastProperty] = keep + // check to make sure that we didn't make an empty property + // remove the property key if so + for (let p in parent) { + if (Array.isArray(parent[p])) { + if (parent[p].length === 0) { + delete parent[p] + } + } + } - if (parent[lastProperty].length==0){ - delete parent[lastProperty] - } - // console.log("--------pt 5------------") - // console.log(JSON.stringify(pt,null,2)) + // console.log("post filter:",parent) + // console.log("The PT",pt) + // make sure we don't leave any blank blank nodes behind + pt.userValue = utilsProfile.pruneUserValue(pt.userValue) - } + // they changed something + this.dataChanged() - // make sure the blank node is not empty either - // loop through the property list and check the parents + } else { + console.error('removeValueSimple: Cannot locate the component by guid', componentGuid, this.activeProfile) + } - // if (parent && Object.keys(parent).length==2 && parent['@type'] && parent['@guid']) { - propertyPath.pop() - let uv = pt.userValue - let oldUv = pt.userValue + }, - for (let p of propertyPath){ - uv = uv[p.propertyURI] - if (Array.isArray(uv)){ - uv=uv[0] - } - if (Object.keys(uv).length == 2 && uv['@guid'] && uv['@type']){ - // it is an empty bnode, delete it - delete oldUv[p.propertyURI] - }else if (Object.keys(uv).length == 1 && uv['@guid']){ - delete oldUv[p.propertyURI] - } - // console.log(p.propertyURI,'has',Object.keys(uv).length,'keys') - // the oldUv so we have a references to where we will be in the next loop so we can delete from the parent obj - oldUv = oldUv[p.propertyURI] - if (Array.isArray(oldUv)){ - oldUv=oldUv[0] - } - } + /** + * This removes the values of a simple lookup field + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} fieldGuid - the guid of the field + * @return {void} + */ + removeValueSimple: async function (componentGuid, fieldGuid) { + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - // console.log("Delete this guy",propertyPath ) - // } + if (pt !== false) { - // also remove the paired literal lines if needed - // console.log("Building lines") - utilsParse.buildPairedLiteralsIndicators(this.activeProfile) + // is it a top level property, meaning there is no need to dig through the blank nodes of the userValue + if (pt.valueConstraint.valueTemplateRefs.length === 0) { + // filter the entire uservalue + for (let key in pt.userValue) { - } + if (Array.isArray(pt.userValue[key])) { + pt.userValue[key] = pt.userValue[key].filter((v) => { + if (v && v['@guid'] && v['@guid'] === fieldGuid) { + return false + } else { + return true + } + }) + } + } + for (let key in pt.userValue) { + if (Array.isArray(pt.userValue[key])) { + if (pt.userValue[key].length === 0) { + delete pt.userValue[key] + } + } + } + this.dataChanged() + return true - // this will only trigger on adding new literal events, like scriptshifter, not editing - if (checkLiteralOrder && Array.isArray(checkLiteralOrder) && checkLiteralOrder.length>1){ - if (pt.userValue){ - let propsFirstLevel = Object.keys(pt.userValue).filter(v => { return !v.startsWith('@') }) - for (let p1 of propsFirstLevel){ - for (let bnode of pt.userValue[p1]){ - let propsSecondLevel = Object.keys(bnode).filter(v => { return !v.startsWith('@') }) - for (let p2 of propsSecondLevel){ - if (Array.isArray(bnode[p2]) && bnode[p2].length>1){ - if (bnode[p2].filter((v)=>{ return (v['@language'])}).length>0){ - // sort the array of literals so the latin one is first - if (usePreferenceStore().returnValue('--b-edit-main-literal-non-latin-first')){ - bnode[p2] = this.sortObjectsByLatinMatch(bnode[p2],p2 ).reverse() - }else{ - bnode[p2] = this.sortObjectsByLatinMatch(bnode[p2],p2 ) - } - } - } } - } - } - } - // also build the paired literal lines - this.reorderAllNonLatinLiterals() - utilsParse.buildPairedLiteralsIndicators(this.activeProfile) - } + // find the correct blank node to edit if possible, if we don't find it then we need to create it + // console.log(pt) + // console.log("fieldGuid",fieldGuid) - // this is a hook to watch for MLC numbering configuration - // if they add a divsion to the MLC number or remove it then we want to flip a flag - // so that the next MLC number generated will also insert the divsion or not - if (propertyPath.some((pp) => pp.propertyURI.includes("http://id.loc.gov/ontologies/bibframe/classificationPortion")) && value.startsWith("MLC")){ - console.log("--------MLC change detected------------") - let mlcMatch = value.match(/^MLC[SMLF]([A-Z])/i) - if (mlcMatch) { - usePreferenceStore().setValue('--b-shelflist-mlc-division',mlcMatch[1].toUpperCase()) - } else { - usePreferenceStore().setValue('--b-shelflist-mlc-division',"") - } - } + let parent = utilsProfile.returnGuidParent(pt.userValue, fieldGuid) + // just look through all of the properties, if its an array filter it + for (let p in parent) { + if (Array.isArray(parent[p])) { + parent[p] = parent[p].filter((v) => { - // console.log("Before prune") - // console.log(JSON.stringify(pt.userValue)) + if (v && v['@guid'] && v['@guid'] === fieldGuid) { + return false + } else { + return true + } + }) + } if (['object'].includes(typeof parent[p]) && parent[p] !== null) { + + // check the parent if there are only two keys this is a top level + // simple lookup, blank out the non-root key and that should clear this value + // if (Object.keys(pt.userValue).length==2){ + // for (let k in pt.userValue){ + // if (k != '@root'){ + // pt.userValue[k] = [] + // } + // } + // } + + // // not an array just remove the values + // for (let key in parent[p]){ + // if (key !='@guid'){ + // delete parent[p][key] + // } + // } - // pt.userValue = utilsProfile.pruneUserValue(pt.userValue) - // console.log("affter prune") - // console.log(JSON.stringify(pt.userValue)) + } + } + - // they changed something - this.dataChanged() - }else{ - console.error('setValueLiteral: Cannot locate the component by guid', componentGuid, this.activeProfile) - } - }, + // check to make sure that we didn't make an empty property + // remove the property key if so + for (let p in parent) { + if (Array.isArray(parent[p])) { + if (parent[p].length === 0) { + delete parent[p] + } + } + } - /** - * returns a literal value of field - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value - * @return {array} - an array of objs representing the literals - */ - returnLiteralValueFromProfile: function(componentGuid, propertyPath){ + // see if we removed it from those actions + parent = utilsProfile.returnGuidParent(pt.userValue, fieldGuid) - // for the electronic locator, the path ends with `sameAs`, but it just gets in the way, toss it - let isLocator = propertyPath.some((pp) => pp.propertyURI.includes("electronicLocator") || pp.propertyURI.includes("supplementaryContent") ) - if (isLocator){ - // `sameAs` gets in the way for the electronicLocator, toss it - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.w3.org/2002/07/owl#sameAs') }) + if (parent !== false) { - //The propertyPath for supplementaryContent's note is missing the note. It jumps straight to the label - // fix the propertyPath so the XML can get built correctly - if (propertyPath.some((pp) => pp.propertyURI.includes("supplementaryContent")) && propertyPath.at(-1).propertyURI == "http://www.w3.org/2000/01/rdf-schema#label"){ - propertyPath.splice(1, 0, { level: 1, propertyURI: "http://id.loc.gov/ontologies/bibframe/note" }) - propertyPath.at(-1).level = 2 - } - } + for (let p in parent) { + if (['object'].includes(typeof parent[p]) && parent[p] !== null) { - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - let valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) - let deepestLevelURI = propertyPath[propertyPath.length-1].propertyURI + // check the parent if there are only two keys this is a top level + // simple lookup, blank out the non-root key and that should clear this value + if (Object.keys(pt.userValue).length == 2) { - // console.log(propertyPath[0], deepestLevelURI) - // console.log('pt',pt) - // console.log(valueLocation) + // if they have a @type keep it around for later so it remains that type when the clear the value + // so they dont need to change interface back to that reference component + let oldType = null + let oldTypeParent = null - if (valueLocation){ + for (let k in pt.userValue) { + if (pt.userValue[k] && pt.userValue[k][0] && pt.userValue[k][0]['@type']) { + oldType = pt.userValue[k][0]['@type'] + oldTypeParent = k + } + } - let values = [] + for (let k in pt.userValue) { + if (k != '@root') { + delete pt.userValue[k] + } + } + // set the old type back so it doesn't change the reference select in the user interface + if (oldType && oldTypeParent) { + pt.userValue[oldTypeParent] = [ + { + '@type': oldType + } + ] + } - for (let v of valueLocation){ + } - // console.log('v->',v) + // // not an array just remove the values + // for (let key in parent[p]){ + // if (key !='@guid'){ + // delete parent[p][key] + // } + // } - if (v[deepestLevelURI]){ - values.push({ - '@guid':v['@guid'], - value: unescape(v[deepestLevelURI]), - '@language' : (v['@language']) ? v['@language'] : null, - }) - } else if (isLocator){ //for electronicLocator, incoming records have the value in `@id` - values.push({ - '@guid':v['@guid'], - value: unescape(v["@id"]), - '@language' : (v['@language']) ? v['@language'] : null, - }) - }else{ - return false - //console.warn('While looking for ',deepestLevelURI, ' could not find in ', pt, 'valueLocation:',valueLocation) - } - } - return values + } + } - } - // will return false if here - return valueLocation + } + // they changed something + this.dataChanged() - }, + // console.log("psot filter em:",parent) - /** - * returns a simple lookup value of field - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value - * @return {array} - an array of objs representing the simple lookup values - */ - returnSimpleLookupValueFromProfile: function(componentGuid, propertyPath){ - - // TODO: reconcile this to how the profiles are built, or dont.. - // remove the sameAs from this property path, which will be the last one, we don't need it - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.w3.org/2002/07/owl#sameAs') }) - - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - let valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) - - // let deepestLevelURI = propertyPath[propertyPath.length-1].propertyURI - - if (!valueLocation){ - // if we did not find it at that level then try removing any issues inthe property path - propertyPath = propertyPath.filter((v)=> { - // remove any classnames - // like http://id.loc.gov/ontologies/bibframe/PlaybackCharacteristic - if (!v.propertyURI.match(/http:\/\/id\.loc\.gov\/ontologies\/bibframe\/[A-Z][a-z]+/)){ - return true - }else{ - return false - } - }) - if (propertyPath.length>0){ - valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) - } - } - if (valueLocation){ + // if (blankNode === false){ - let values = [] - for (let v of valueLocation){ + // }else{ - let URI = null - let label = null - if (v['@id']){ - URI = v['@id'] - } - for (let lP of LABEL_PREDICATES){ - if (v[lP] && v[lP][0][lP]){ - label = v[lP][0][lP] - break + // } + } else { + console.error('removeValueSimple: Cannot locate the component by guid', componentGuid, this.activeProfile) } - } - let uneditable = false - // if it is deepHierarchy then then we are copy pasting what came into the system and they cann change it anyway. - if (pt.deepHierarchy){uneditable=true} + }, - if (URI && label){ - values.push({ - '@guid':v['@guid'], - URI: URI, - label: label, - needsDereference: false, - uneditable:uneditable, - isLiteral: false, - }) - }else if (URI && !label){ - values.push({ - '@guid':v['@guid'], - URI: URI, - label: label, - needsDereference: true, - uneditable:uneditable, - isLiteral: false, - }) - }else if (!URI && label){ - values.push({ - '@guid':v['@guid'], - URI: URI, - label: label, - needsDereference: false, - uneditable:uneditable, - isLiteral: true, - }) - } - } - return values - } + /** + * Sets a literal value of field + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} fieldGuid - the guid of the field + * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value + * @param {string} lang - the ISO rdf language value like 'en' to append to the literal 'xxxxx@en' + * @return {void} + */ + setValueLiteral: function (componentGuid, fieldGuid, propertyPath, value, lang, repeatedLiteral) { + // console.info("--------------------------\nsetValueLiteral") + // console.info("\tcomponentGuid: ", componentGuid) + // console.info("\tfieldGuid: ", fieldGuid) + // console.info("\tpropertyPath: ", propertyPath) + // console.info("\tvalue: ", value) + // console.info("\tlang: ", lang) + // componentGuid: aiPuH4YsetZ9xmcv7rqisJ + // fieldGuid: pdtUXGpNDJ9mz33JM3uxje - // if valueLocation is false then it did not find anytihng meaning its empty, return empty array - return [] + // from NAR, fieldGuid is null + // remove returns from value + value = value.replace(/[\n\r]+/g, ''); - }, + // make a copy of the property path, dont modify the linked one passed + propertyPath = JSON.parse(JSON.stringify(propertyPath)) - /** - * returns a complex lookup value of field - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value - * @return {array} - an array of objs representing the simple lookup values - */ - returnComplexLookupValueFromProfile: function(componentGuid, propertyPath){ - // TODO: reconcile this to how the profiles are built, or dont.. - // remove the sameAs from this property path, which will be the last one, we don't need it - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.w3.org/2002/07/owl#sameAs') }) - // TODOL reconcile this in the profiles (!!!) - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.loc.gov/mads/rdf/v1#componentList') }) - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.loc.gov/mads/rdf/v1#Topic') }) - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.loc.gov/mads/rdf/v1#Geographic') }) + //The propertyPath for supplementaryContent's note is missing the note. It jumps straight to the label + // so insert it so XML can get built + if (propertyPath.some((pp) => pp.propertyURI.includes("supplementaryContent")) && propertyPath.at(-1).propertyURI == "http://www.w3.org/2000/01/rdf-schema#label") { + propertyPath.splice(1, 0, { level: 1, propertyURI: "http://id.loc.gov/ontologies/bibframe/note" }) + propertyPath.at(-1).level = 2 + } + // this needs to include a check for "supplementaryContent", so the note will populate in the form + let isLocator = propertyPath.some((pp) => pp.propertyURI.includes("electronicLocator") || pp.propertyURI.includes("supplementaryContent")) + // for the electronic locator, the path ends with `sameAs`, but it just gets in the way, toss it + if (isLocator) { + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.w3.org/2002/07/owl#sameAs') }) + } - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - let valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) - let deepestLevelURI = propertyPath[propertyPath.length-1].propertyURI + let lastProperty = propertyPath.at(-1).propertyURI + // locate the correct pt to work on in the activeProfile + let pt + if (cachePt[componentGuid]) { + pt = cachePt[componentGuid] + } else { + pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + cachePt[componentGuid] = pt + } - // console.log(pt.propertyURI) - // console.log("propertyPath=",propertyPath) + // used later to sort the literal order if needed + let checkLiteralOrder = null - // working with Hubs as subjects breaks a few things. - // rewrite the property Path if we are working with them - if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject'){ + //clear the cache if the value was deleted + if (value.trim() == "") { + if (Object.keys(cachePt).includes(componentGuid)) { + delete cachePt[componentGuid] + } + for (let guid of Object.keys(cacheGuid)) { + cleanCacheGuid(cacheGuid, JSON.parse(JSON.stringify(pt.userValue)), guid) + } + } - if (pt.userValue && - pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type'] && - (pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/Hub" || pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/Work") - ) + // console.log("--------pt 1------------") + // console.log(JSON.stringify(pt,null,2)) + // let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) + // console.log(componentGuid, fieldGuid, propertyPath, value, lang, repeatedLiteral) + if (pt !== false) { + pt.hasData = true + pt.userModified = true + pt.dataLoaded = false + + // find the correct blank node to edit if possible, if we don't find it then we need to create it + let blankNode + if (cacheGuid[fieldGuid]) { + blankNode = cacheGuid[fieldGuid] + } else { + blankNode = utilsProfile.returnGuidLocation(pt.userValue, fieldGuid) + cacheGuid[fieldGuid] = blankNode + } - // it is a subject remove label properties - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=="http://www.loc.gov/mads/rdf/v1#authoritativeLabel") }) - valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) + // console.log("--------pt 2------------") + // console.log(JSON.stringify(pt,null,2)) - // console.log("NEW propertyPath=",propertyPath) + // let blankNode = utilsProfile.returnGuidLocation(pt.userValue,fieldGuid) - } + if (blankNode === false) { + // create the path to the blank node + let buildBlankNodeResult - if (valueLocation){ + let currentValueCount = utilsProfile.countValues(pt, propertyPath) - let values = [] + if (currentValueCount === 0) { + // this is the first value, we need to construct the hierarchy to the bnode + buildBlankNodeResult = utilsProfile.buildBlanknode(pt, propertyPath, true) - for (let v of valueLocation){ - let URI = null - let label = null + // console.log("buildBlankNodeResult",JSON.stringify(buildBlankNodeResult,null,2)) + pt = buildBlankNodeResult[0] - if (v['@id']){ - URI = v['@id'] - } + // now we can make a link to the parent of where the literal value should live + blankNode = utilsProfile.returnGuidLocation(pt.userValue, buildBlankNodeResult[1]) - for (let lP of LABEL_PREDICATES){ - if (v[lP] && v[lP][0][lP]){ - label = v[lP][0][lP] - break - } - } + // console.log("blankNode",JSON.stringify(blankNode,null,2)) + // this is a new node, so we want to overwrite the guid created in the build process + // with the one that was already created in the userinterface + blankNode['@guid'] = fieldGuid + // set a temp value that will be over written below + blankNode[lastProperty] = true + // console.log("--------pt 3------------") + // console.log(JSON.stringify(pt,null,2)) - // look for bf:title -> bf:mainTitle - if (!label){ - for (let lP1 of LABEL_PREDICATES){ - for (let lP2 of LABEL_PREDICATES){ - if (v[lP1] && v[lP1][0][lP2] && v[lP1][0][lP2][0][lP2]){ - label = v[lP1][0][lP2][0][lP2] - break - } - } - } - } - - let source = null - if (URI && URI.indexOf('/fast/') >1){ - source = 'FAST' - } - let uneditable = false - - // if we don't have a URI for a work don't let them edit it - if (!URI && label && v['@type'] && v['@type'] == 'http://id.loc.gov/ontologies/bibframe/Work'){ - uneditable = true - } - if (!URI && label && v['@type'] && v['@type'] == 'http://id.loc.gov/ontologies/bflc/Uncontrolled'){ - uneditable = true - } - if (!URI && label && v['@type'] && v['@type'] == 'http://id.loc.gov/ontologies/bibframe/Uncontrolled'){ - uneditable = true - } - - // always allow editing subjects - if (pt && pt.propertyURI && pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/subject"){ - uneditable = false - } - - // if it is deepHierarchy then then we are copy pasting what came into the system and they cann change it anyway. - if (pt.deepHierarchy){uneditable=true} - - if (URI && label){ - values.push({ - '@guid':v['@guid'], - URI: URI, - label: label, - source: source, - needsDereference: false, - isLiteral: false, - uneditable: uneditable, - type:v['@type'] - }) - }else if (URI && !label){ - values.push({ - '@guid':v['@guid'], - URI: URI, - label: label, - source: source, - needsDereference: true, - isLiteral: false, - uneditable: uneditable, - type:v['@type'] - }) - }else if (!URI && label){ - - values.push({ - '@guid':v['@guid'], - URI: URI, - label: label, - source: source, - needsDereference: false, - uneditable: uneditable, - isLiteral: true, - type:v['@type'] - }) - } + } else { + // there is already values here, so we need to insert a new value into the hiearchy - } + let parent = utilsProfile.returnPropertyPathParent(pt, propertyPath) + if (!parent) { + console.error("Trying to add second literal, could not find the property path parent", pt) + return false + } - return values - } + if (!parent[lastProperty]) { + console.error('Trying to find the value of this literal, unable to:', componentGuid, fieldGuid, propertyPath, value, lang, pt) + return false + } + let newGuid = short.generate() + // make a place for it + parent[lastProperty].push( + { + '@guid': newGuid, + } + ) - // if valueLocation is false then it did not find anytihng meaning its empty, return empty array - return [] + checkLiteralOrder = parent[lastProperty] + // get a link to it we'll edit it below + blankNode = utilsProfile.returnGuidLocation(pt.userValue, newGuid) + // set a temp value that will be over written below + blankNode[lastProperty] = true + // console.log("--------pt 4------------") + // console.log(JSON.stringify(pt,null,2)) + } - }, + // They used "Additional literal" on an empty field, add the new field + // without this, the user needs to run the action twice to the the additional field + if (currentValueCount === 0 && value == 'new value') { + // there is already values here, so we need to insert a new value into the hiearchy + let parent = utilsProfile.returnPropertyPathParent(pt, propertyPath) - /** - * Sets a "Complex lookup" value, things from large lookups like NAF LCSH, etc - * this function only creates new values, does not modify (aka delete) - * @async - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} fieldGuid - the guid of the field - * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value - * @param {string} URI - the URI for the value - * @param {string} label - the label to use - * @param {string} type - the URI of the type to use, like http://www.loc.gov/mads/rdf/v1#CorporateName - - * @return {void} - */ - setValueComplex: async function(componentGuid, fieldGuid, propertyPath, URI, label, type, nodeMap=null, marcKey=null ){ - // TODO: reconcile this to how the profiles are built, or dont.. - // remove the sameAs from this property path, which will be the last one, we don't need it - propertyPath = propertyPath.filter((v)=> { return (v.propertyURI!=='http://www.w3.org/2002/07/owl#sameAs') }) - // console.log("propertyPath=",propertyPath) - - let lastProperty = propertyPath.at(-1).propertyURI - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - // if (!type && URI && !lastProperty.includes("intendedAudience")){ - // // I regretfully inform you we will need to look this up - // let context = await utilsNetwork.returnContext(URI) - // type = context.typeFull - // if (!marcKey){ - // marcKey = context.marcKey - // } - // } - - if (['Work', 'Hub', 'GenreForm'].includes(type)){ - type = "http://id.loc.gov/ontologies/bibframe/" + type - } - if (type && !type.startsWith("http")){ - type = "http://www.loc.gov/mads/rdf/v1#" + type - } - - // literals don't have a type or a URI & intendedAudience has extra considerations - // namely that the rdf:Type in BF is bf:Authority - if ((!type && !URI) || lastProperty.includes("intendedAudience")){ - type = await utilsRDF.suggestTypeProfile(lastProperty, pt) - if (type == false){ - type = await utilsRDF.suggestTypeNetwork(lastProperty) - } - } - - // More granual typing for Geographic - if (type == "http://www.loc.gov/mads/rdf/v1#Geographic"){ - if (propertyPath.filter((v) => { return v.propertyURI == 'http://id.loc.gov/ontologies/bibframe/geographicCoverage' }).length > 0){ - type = "http://id.loc.gov/ontologies/bibframe/GeographicCoverage" - } else { - type = "http://id.loc.gov/ontologies/bibframe/Place" - } - } - - if (pt !== false){ - pt.hasData = true - pt.userModified = true - pt.dataLoaded = false - - // find the correct blank node to edit if possible, if we don't find it then we need to create it - let blankNode = utilsProfile.returnGuidLocation(pt.userValue,fieldGuid) - - if (blankNode === false){ - // create the path to the blank node - let buildBlankNodeResult = await utilsProfile.buildBlanknode(pt,propertyPath) - - pt = buildBlankNodeResult[0] - - // now we can make a link to the parent of where the literal value should live - blankNode = utilsProfile.returnGuidLocation(pt.userValue,buildBlankNodeResult[1]) - - //empty out the blankNode's existing data so it only has the new data - for (let key of Object.keys(blankNode).filter((k) => !k.startsWith("@"))){ - blankNode[key] = [] - } - - // set the URI - // if its null then we are adding a literal - if (URI !== null){ - blankNode['@id'] = URI - }else{ - // do nothing for now... - } - - // overwrite whatever the helper methods set the type to for this one, we know the final - // type and what it needs to be - // don't do this for subjects, Subjects as complexValues will add extra nesting that doesn't match a subject's XML - if (!propertyPath.some((pp) => pp.propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject')){ - blankNode['@type'] = type - } - - - if (!Array.isArray(label)){ - label = [label] - } - - for (let aLabelNode of label){ - - if (!blankNode['http://www.w3.org/2000/01/rdf-schema#label']){ - blankNode['http://www.w3.org/2000/01/rdf-schema#label'] = [] - } - if (typeof aLabelNode == 'string'){ - blankNode['http://www.w3.org/2000/01/rdf-schema#label'].push( - { - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : aLabelNode - } - ) - }else if (aLabelNode['@value']){ - let aNode = { - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : aLabelNode['@value'] - } - if (aLabelNode['@language']){ - aNode['@language']=aLabelNode['@language'] - } - blankNode['http://www.w3.org/2000/01/rdf-schema#label'].push(aNode) - - }else{ - console.error("Cannot understand response from context extaction for label:",label) - } - } - - // add in the venacular labels - if (nodeMap && nodeMap.vernacularLabels){ - for (let l of nodeMap.vernacularLabels){ - // make sure there really is a non-latin label - if (l.indexOf("@") == -1){ - continue - } - // the api returns it as "label@language" - let lLabel = l.split("@")[0] - let lLanguage = l.split("@")[1] - // make sure there is a label field for it - if (!blankNode['http://www.w3.org/2000/01/rdf-schema#label']){ - blankNode['http://www.w3.org/2000/01/rdf-schema#label'] = [] - } - - blankNode['http://www.w3.org/2000/01/rdf-schema#label'].push( - { - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : lLabel, - '@language': lLanguage - } - ) - } - } - - - - // if (URI.indexOf('n2010057779') > -1){ - // nodeMap.vernacularMarcKeys = ["4001 $a玄 武岩,$d1969-@zxx-Hani","4001 $a현 무암,$d1969-@zxx-Hang"] - // } - - // console.log("nodeMap",nodeMap) - - //Add gacs code to user data - if (nodeMap["gacs"]){ - blankNode["http://www.loc.gov/mads/rdf/v1#code"] = [] - for (let code in nodeMap["gacs"]){ - blankNode["http://www.loc.gov/mads/rdf/v1#code"].push( - { - '@guid': short.generate(), - "@gacs": "http://id.loc.gov/datatypes/codes/gac", - 'http://www.loc.gov/mads/rdf/v1#code': nodeMap["gacs"][code] - } - ) - } - } + if (!parent) { + console.error("Trying to add second literal, could not find the property path parent", pt) + return false + } - if (!Array.isArray(marcKey)){ - marcKey = [marcKey] - } - - for (let aMarcKeyNode of marcKey){ - - // console.log("aMarcKeyNode",aMarcKeyNode) - - if (!blankNode['http://id.loc.gov/ontologies/bflc/marcKey']){ - blankNode['http://id.loc.gov/ontologies/bflc/marcKey'] = [] - } - if (typeof aMarcKeyNode == 'string'){ - blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push( - { - '@guid': short.generate(), - 'http://id.loc.gov/ontologies/bflc/marcKey' : aMarcKeyNode - } - ) - }else if (aMarcKeyNode && aMarcKeyNode['@value']){ - let aNode = { - '@guid': short.generate(), - 'http://id.loc.gov/ontologies/bflc/marcKey' : aMarcKeyNode['@value'] - } - if (aMarcKeyNode['@language']){ - aNode['@language']=aMarcKeyNode['@language'] - } - blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push(aNode) - }else if (aMarcKeyNode && aMarcKeyNode.marcKey){ - - let aNode = { - '@guid': short.generate(), - 'http://id.loc.gov/ontologies/bflc/marcKey' : aMarcKeyNode.marcKey - } - if (aMarcKeyNode['@language']){ - aNode['@language']=aMarcKeyNode['@language'] - } - - blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push(aNode) - - }else{ - console.error("Cannot understand response from context extaction for marcKey:",marcKey) - } - } - - // add in the venacular marckeys - if (nodeMap && nodeMap.vernacularMarcKeys){ - for (let l of nodeMap.vernacularMarcKeys){ - // make sure there really is a non-latin label - if (l.indexOf("@") == -1 && l.indexOf("(bcp47)") == -1){ - continue - } - - // the api returns it as "label@language" or (bcp47)language - let lLabel = '' - let lLanguage = '' - - if (l.includes("@")){ - lLabel = l.split("@")[0] - lLanguage = l.split("@")[1] - } else if (l.includes("bcp47")){ - lLabel = l - lLanguage = l.split("(bcp47)")[1] - } - - // make sure there is a label field for it - if (!blankNode['http://id.loc.gov/ontologies/bflc/marcKey']){ - blankNode['http://id.loc.gov/ontologies/bflc/marcKey'] = [] - } - - blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push( - { - '@guid': short.generate(), - 'http://id.loc.gov/ontologies/bflc/marcKey' : lLabel, - '@language': lLanguage - } - ) - } - - } - - - - - - // if (nodeMap["marcKey"]){ - // blankNode["http://id.loc.gov/ontologies/bflc/marcKey"] = [ - // { - // '@guid': short.generate(), - // 'http://id.loc.gov/ontologies/bflc/marcKey': nodeMap["marcKey"][0] - // } - // ] - // } - - // add the source for Genre/Form - if (propertyPath.map((obj) => obj.propertyURI).includes("http://id.loc.gov/ontologies/bibframe/genreForm")){ - let objId = blankNode['@id'] - if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/authorities/genreForms/collection_LCGFT_General')){ - blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ - { - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/Source", - "@id": "http://id.loc.gov/authorities/genreForms/collection_LCGFT_General", - "http://www.w3.org/2000/01/rdf-schema#label": [ - { - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress genre/form terms for library and archival materials" - } - ] - } - ] - } if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/vocabulary/rbms/collection_rbmscv')){ - blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ - { - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/Source", - "@id": "http://id.loc.gov/vocabulary/genreFormSchemes/rbmscv", - "http://www.w3.org/2000/01/rdf-schema#label": [ - { - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "RBMS Controlled Vocabularies" - } - ] - } - ] - } - } - - // add source for LCDGT - if (propertyPath.map((obj) => obj.propertyURI).includes("http://id.loc.gov/ontologies/bibframe/intendedAudience")){ - let objId = blankNode['@id'] - if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/authorities/demographicTerms/collection_LCDGT_General')){ - blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ - { - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/Source", - "@id": "http://id.loc.gov/authorities/demographicTerms/collection_LCDGT_General", - "http://www.w3.org/2000/01/rdf-schema#label": [ - { - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress demographic group term and code list" - } - ] - } - ] - } - if (nodeMap.collections && nodeMap.collections.includes("http://id.loc.gov/vocabulary/maudience/collection_maudience")){ - blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ - { - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/Source", - "@id": "http://id.loc.gov/vocabulary/maudience/collection_maudience", - "http://www.w3.org/2000/01/rdf-schema#label": [ - { - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "MARC Audience" - } - ] - } - ] - } - } - if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/authorities/subjects/collection_LCSHAuthorizedHeadings')){ - blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ - { - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/Source", - "@id": "http://id.loc.gov/authorities/subjects/collection_LCSHAuthorizedHeadings", - "http://www.w3.org/2000/01/rdf-schema#label": [ - { - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress subject headings" + if (!parent[lastProperty]) { + console.error('Trying to find the value of this literal, unable to:', componentGuid, fieldGuid, propertyPath, value, lang, pt) + return false } - ] - } - ] - } + let newGuid = short.generate() - - - - }else{ - let parent = utilsProfile.returnGuidParent(pt.userValue,fieldGuid) - - // make sure we can find where to put the new one - if (parent[lastProperty]){ - // create a new node here - parent[lastProperty].push({ - '@id': URI, - '@guid' : short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : [ - { - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label' : label + // make a place for it + parent[lastProperty].push( + { + '@guid': newGuid, + } + ) + checkLiteralOrder = parent[lastProperty] + // get a link to it we'll edit it below + blankNode = utilsProfile.returnGuidLocation(pt.userValue, newGuid) + // set a temp value that will be over written below + blankNode[lastProperty] = true + } + // console.log("currentValueCount",currentValueCount) } - ] - }) + if (!blankNode[lastProperty]) { + console.error('Trying to find the value of this literal, unable to:', componentGuid, fieldGuid, propertyPath, value, lang, pt) + } + if (lang == "REMOVE_COMMAND") { + delete blankNode['@language'] + lang = null + } - }else{ - console.error("Could not find the parent[lastProperty] of the existing value", {'parent':parent,'pt.userValue':pt.userValue, 'fieldGuid':fieldGuid}) - } + if (lang) { + blankNode['@language'] = lang + } + // and now add in the literal value into the correct property + blankNode[lastProperty] = value - } - // they changed something + // for electronicLocators, update the ID, so the XML can get built correctly + if (isLocator && Object.keys(blankNode).some((key) => key == "http://id.loc.gov/ontologies/bibframe/electronicLocator")) { + blankNode["@id"] = value + } - this.dataChanged() + // if we just set an empty value, remove the value property, and if there are no other values, remvoe the entire property + if (value.trim() === '') { + delete blankNode[lastProperty] + + let parent = utilsProfile.returnPropertyPathParent(pt, propertyPath) + + if (parent && parent[lastProperty]) { + let keep = [] + if (parent[lastProperty].length > 0) { + for (let value of parent[lastProperty]) { + // does it have a value? + if (value[lastProperty] && value[lastProperty] != '') { + keep.push(value) + } else { + // console.log("dumping",value) + } + } + } - }else{ - console.error('setValueComplex: Cannot locate the component by guid', componentGuid, this.activeProfile) - } + parent[lastProperty] = keep - console.log("pt is ", JSON.stringify(pt.userValue)) - }, + if (parent[lastProperty].length == 0) { + delete parent[lastProperty] + } - /** - * sets the uservalue when dealing with subject headings - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {array} subjectComponents - objects with the data to represent the component parts of subject heading - * @param {array} propertyPath - the path of uris to get to this level - * @return {void} - - */ - setValueSubject: async function(componentGuid,subjectComponents,propertyPath){ - // we're just going to overwrite the whole userValue with the constructed headings - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - // console.log('-----') - // console.log(pt) - // console.log(subjectComponents) - // console.log(propertyPath) - - // find it - if (pt){ - // build out the hiearchy - let userValue = {} - - // build the hiearchy if it doesn't exist to place the data - - // we are adding the rdfs:label and the @id to this property - // we just need to know where to put it - - // a reference to allow us to write to the end of the hierarchy - let currentUserValuePos = userValue - - // used as a reference to the last postion's parent, so we can easily add a new sibling - // let currentUserValuePosParent = null - - // keeps track of the @type, will be the last @type of the hiearchy when done looping - let thisLevelType - let complexCheck = false - for (let p of propertyPath){ - // if the property is owl:sameAs it is the last field - // of where we are building the entitiy, so we don't want - // bf:Agent -> owl:sameAs we just want the bf:Agent with values filed in there - if (p.propertyURI=='http://www.w3.org/2002/07/owl#sameAs'){ - break - } - // same with component list, we'll build that manually - if (p.propertyURI=='http://www.loc.gov/mads/rdf/v1#componentList'){ - break - } + // console.log("--------pt 5------------") + // console.log(JSON.stringify(pt,null,2)) - if (!currentUserValuePos[p.propertyURI]){ - currentUserValuePos[p.propertyURI] = [] - } - let thisLevelType = utilsRDF.suggestTypeProfile(p.propertyURI,pt) - if (thisLevelType === false){ - // did not find it in the profile, look to the network - thisLevelType = await utilsRDF.suggestTypeNetwork(p.propertyURI) - } + } - // if it's a complexSubject, replace bf:Topic with madsrdf:ComplexSubject -- the conversion expects this - if (thisLevelType == "http://id.loc.gov/ontologies/bibframe/Topic" && propertyPath.some((obj) => obj.propertyURI == "http://www.loc.gov/mads/rdf/v1#componentList")){ - // thisLevelType = 'madsrdf:ComplexSubject' - complexCheck = true - } + // make sure the blank node is not empty either + // loop through the property list and check the parents - let thisLevel = {'@guid':short.generate()} - if (!utilsRDF.isUriALiteral(thisLevelType)){ - thisLevel['@type'] = thisLevelType - } - if (currentUserValuePos[p.propertyURI].length==0){ - currentUserValuePos[p.propertyURI].push(thisLevel) - } - // currentUserValuePosParent = currentUserValuePos[p.propertyURI] - currentUserValuePos = currentUserValuePos[p.propertyURI][0] - } + // if (parent && Object.keys(parent).length==2 && parent['@type'] && parent['@guid']) { - pt.hasData = true - pt.userModified = true - pt.dataLoaded = false + propertyPath.pop() + let uv = pt.userValue + let oldUv = pt.userValue - if (pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"] && - pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0] && - pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"] && - pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"][0]){ + for (let p of propertyPath) { + uv = uv[p.propertyURI] + if (Array.isArray(uv)) { + uv = uv[0] + } + if (Object.keys(uv).length == 2 && uv['@guid'] && uv['@type']) { + // it is an empty bnode, delete it + delete oldUv[p.propertyURI] + } else if (Object.keys(uv).length == 1 && uv['@guid']) { + delete oldUv[p.propertyURI] + } - userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"] = JSON.parse(JSON.stringify(pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"])) - } + // console.log(p.propertyURI,'has',Object.keys(uv).length,'keys') - if (pt.userValue['@root']){ - userValue['@root'] = JSON.parse(JSON.stringify(pt.userValue['@root'])) - } - if (pt.userValue['@guid']){ - userValue['@guid'] = JSON.parse(JSON.stringify(pt.userValue['@guid'])) - } + // the oldUv so we have a references to where we will be in the next loop so we can delete from the parent obj + oldUv = oldUv[p.propertyURI] + if (Array.isArray(oldUv)) { + oldUv = oldUv[0] + } - currentUserValuePos["http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] = [{ - "@guid": short.generate(), - "@id": "http://id.loc.gov/authorities/subjects" - }] + } - // if it is a solo subject heading - if (subjectComponents.length==1){ - // it might be a literal. - if (subjectComponents[0].uri){ - currentUserValuePos['@id'] = subjectComponents[0].uri - }else{ - delete currentUserValuePos["http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] - } + // console.log("Delete this guy",propertyPath ) + // } - // it might be a Hub if so its not a isMemberOfMADSScheme subject - if (subjectComponents[0].type && subjectComponents[0].type.toLowerCase().indexOf("hub")>-1){ - delete currentUserValuePos["http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] - } - if (subjectComponents[0].type){ - currentUserValuePos['@type'] = subjectComponents[0].type.replace('madsrdf:','http://www.loc.gov/mads/rdf/v1#') - }else{ - currentUserValuePos['@type'] = 'madsrdf:Topic' - } + // also remove the paired literal lines if needed + // console.log("Building lines") + utilsParse.buildPairedLiteralsIndicators(this.activeProfile) - // if there is a URI add authorized label - if (currentUserValuePos['@id']){ - currentUserValuePos["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"] = [{ - "@guid": short.generate(), - "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": subjectComponents[0].label - }] } - currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#label"] = [{ - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": subjectComponents[0].label - }] - if (subjectComponents[0].marcKey){ - currentUserValuePos["http://id.loc.gov/ontologies/bflc/marcKey"] = [{ - "@guid": short.generate(), - "http://id.loc.gov/ontologies/bflc/marcKey": subjectComponents[0].marcKey - }] + // this will only trigger on adding new literal events, like scriptshifter, not editing + if (checkLiteralOrder && Array.isArray(checkLiteralOrder) && checkLiteralOrder.length > 1) { + if (pt.userValue) { + let propsFirstLevel = Object.keys(pt.userValue).filter(v => { return !v.startsWith('@') }) + for (let p1 of propsFirstLevel) { + for (let bnode of pt.userValue[p1]) { + let propsSecondLevel = Object.keys(bnode).filter(v => { return !v.startsWith('@') }) + for (let p2 of propsSecondLevel) { + if (Array.isArray(bnode[p2]) && bnode[p2].length > 1) { + if (bnode[p2].filter((v) => { return (v['@language']) }).length > 0) { + // sort the array of literals so the latin one is first + if (usePreferenceStore().returnValue('--b-edit-main-literal-non-latin-first')) { + bnode[p2] = this.sortObjectsByLatinMatch(bnode[p2], p2).reverse() + } else { + bnode[p2] = this.sortObjectsByLatinMatch(bnode[p2], p2) + } + } + } + } + } + } + } + // also build the paired literal lines + this.reorderAllNonLatinLiterals() + utilsParse.buildPairedLiteralsIndicators(this.activeProfile) } - // add in the non latin if present - if (subjectComponents[0].nonLatinLabel && subjectComponents[0].nonLatinLabel.length>0){ - for (let nlL of subjectComponents[0].nonLatinLabel){ - currentUserValuePos["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"].push( - { - "@guid": short.generate(), - "@language": nlL['@language'], - "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": nlL['@value'] - } - ) - currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#label"].push( - { - "@guid": short.generate(), - "@language": nlL['@language'], - "http://www.w3.org/2000/01/rdf-schema#label": nlL['@value'] - } - ) - } - } - if (subjectComponents[0].nonLatinMarcKey && subjectComponents[0].nonLatinMarcKey.length>0){ - for (let nlMK of subjectComponents[0].nonLatinMarcKey){ - currentUserValuePos["http://id.loc.gov/ontologies/bflc/marcKey"].push( - { - "@guid": short.generate(), - "@language": nlMK['@language'], - "http://id.loc.gov/ontologies/bflc/marcKey": nlMK['@value'] - } - ) - } + // this is a hook to watch for MLC numbering configuration + // if they add a divsion to the MLC number or remove it then we want to flip a flag + // so that the next MLC number generated will also insert the divsion or not + if (propertyPath.some((pp) => pp.propertyURI.includes("http://id.loc.gov/ontologies/bibframe/classificationPortion")) && value.startsWith("MLC")) { + console.log("--------MLC change detected------------") + let mlcMatch = value.match(/^MLC[SMLF]([A-Z])/i) + if (mlcMatch) { + usePreferenceStore().setValue('--b-shelflist-mlc-division', mlcMatch[1].toUpperCase()) + } else { + usePreferenceStore().setValue('--b-shelflist-mlc-division', "") + } } + // console.log("Before prune") + // console.log(JSON.stringify(pt.userValue)) - // store.state.activeUndoLog.push(`Added subject heading ${subjectComponents[0].label}`) + // pt.userValue = utilsProfile.pruneUserValue(pt.userValue) + // console.log("affter prune") + // console.log(JSON.stringify(pt.userValue)) - }else if (subjectComponents.length>1){ - //userValue + // they changed something + this.dataChanged() + } else { + console.error('setValueLiteral: Cannot locate the component by guid', componentGuid, this.activeProfile) + } + }, - let fullLabel = subjectComponents.map((c)=>{return c.label}).join('--') + /** + * returns a literal value of field + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value + * @return {array} - an array of objs representing the literals + */ + returnLiteralValueFromProfile: function (componentGuid, propertyPath) { - // store.state.activeUndoLog.push(`Added subject heading ${fullLabel}`) + // for the electronic locator, the path ends with `sameAs`, but it just gets in the way, toss it + let isLocator = propertyPath.some((pp) => pp.propertyURI.includes("electronicLocator") || pp.propertyURI.includes("supplementaryContent")) - currentUserValuePos["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"] = [{ - "@guid": short.generate(), - "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": fullLabel - }] - currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#label"] = [{ - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": fullLabel - }] + if (isLocator) { + // `sameAs` gets in the way for the electronicLocator, toss it + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.w3.org/2002/07/owl#sameAs') }) - if (complexCheck){ - currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#type"] = [{ - "@guid": short.generate(), - "@id": "http://www.loc.gov/mads/rdf/v1#ComplexSubject" - }] + //The propertyPath for supplementaryContent's note is missing the note. It jumps straight to the label + // fix the propertyPath so the XML can get built correctly + if (propertyPath.some((pp) => pp.propertyURI.includes("supplementaryContent")) && propertyPath.at(-1).propertyURI == "http://www.w3.org/2000/01/rdf-schema#label") { + propertyPath.splice(1, 0, { level: 1, propertyURI: "http://id.loc.gov/ontologies/bibframe/note" }) + propertyPath.at(-1).level = 2 } + } - // we need to make the component list then + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + let valueLocation = utilsProfile.returnValueFromPropertyPath(pt, propertyPath) + let deepestLevelURI = propertyPath[propertyPath.length - 1].propertyURI + // console.log(propertyPath[0], deepestLevelURI) + // console.log('pt',pt) + // console.log(valueLocation) - currentUserValuePos["http://www.loc.gov/mads/rdf/v1#componentList"] = [] + if (valueLocation) { - for (let c of subjectComponents){ - let compo = { - "@guid": short.generate(), - "@type": c.type.replace('madsrdf:','http://www.loc.gov/mads/rdf/v1#'), - "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": [{ - "@guid": short.generate(), - "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": c.label - }] - } + let values = [] - if (c.uri){ - compo['@id'] = c.uri - } - if (c.marcKey){ - compo['http://id.loc.gov/ontologies/bflc/marcKey'] = [{ - "@guid": short.generate(), - 'http://id.loc.gov/ontologies/bflc/marcKey': c.marcKey - }] - if (c.nonLatinMarcKey && c.nonLatinMarcKey.length>0){ - for (let nlMK of c.nonLatinMarcKey){ - compo["http://id.loc.gov/ontologies/bflc/marcKey"].push( - { - "@guid": short.generate(), - "@language": nlMK['@language'], - "http://id.loc.gov/ontologies/bflc/marcKey": nlMK['@value'] - } - ) - } - } - } + for (let v of valueLocation) { - if (c.nonLatinLabel && c.nonLatinLabel.length>0){ - for (let nlL of c.nonLatinLabel){ - compo["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"].push( - { - "@guid": short.generate(), - "@language": nlL['@language'], - "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": nlL['@value'] - } - ) - } - } + // console.log('v->',v) - currentUserValuePos["http://www.loc.gov/mads/rdf/v1#componentList"].push(compo) + if (v[deepestLevelURI]) { + values.push({ + '@guid': v['@guid'], + value: unescape(v[deepestLevelURI]), + '@language': (v['@language']) ? v['@language'] : null, + }) + } else if (isLocator) { //for electronicLocator, incoming records have the value in `@id` + values.push({ + '@guid': v['@guid'], + value: unescape(v["@id"]), + '@language': (v['@language']) ? v['@language'] : null, + }) + } else { + return false + //console.warn('While looking for ',deepestLevelURI, ' could not find in ', pt, 'valueLocation:',valueLocation) + } + } + return values - } } - // did they add a LCSH heading, if so add that automatically as a source - for (let h of subjectComponents){ - if (h['uri'] && h['uri'].indexOf('id.loc.gov/authorities/subjects') >-1){ - if (!currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source']){ + // will return false if here + return valueLocation - currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source'] = [ - { - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/Source", - "@id": "http://id.loc.gov/vocabulary/subjectSchemes/lcsh", - "http://www.w3.org/2000/01/rdf-schema#label": [ - { - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress subject headings" - } - ] - } - ] - } - break - } else if (h['uri'] && h['uri'].indexOf('id.loc.gov/authorities/names') >-1){ - if (!currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source']){ + }, - currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source'] = [ - { - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/Source", - // "@id": "http://id.loc.gov/vocabulary/subjectSchemes/naf", - "@id": "http://id.loc.gov/vocabulary/subjectSchemes/lcsh", - "http://www.w3.org/2000/01/rdf-schema#label": [ - { - "@guid": short.generate(), - // "http://www.w3.org/2000/01/rdf-schema#label": "NACO authority file" - "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress subject headings" + /** + * returns a simple lookup value of field + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value + * @return {array} - an array of objs representing the simple lookup values + */ + returnSimpleLookupValueFromProfile: function (componentGuid, propertyPath) { - } - ] - } - ] - } - break - } + // TODO: reconcile this to how the profiles are built, or dont.. + // remove the sameAs from this property path, which will be the last one, we don't need it + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.w3.org/2002/07/owl#sameAs') }) - } + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + let valueLocation = utilsProfile.returnValueFromPropertyPath(pt, propertyPath) - // double check the @type of the resource we want to be as specific as possible - if (currentUserValuePos['@type'] == 'http://www.w3.org/2000/01/rdf-schema#Resource'){ - // this is very generic, try not to use that - if (pt.activeType){ - currentUserValuePos['@type'] = pt.activeType - }else{ - if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length>0){ - let useTemplate = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]] - if (useTemplate && useTemplate.resourceURI){ - currentUserValuePos['@type'] = useTemplate.resourceURI - } + // let deepestLevelURI = propertyPath[propertyPath.length-1].propertyURI + + if (!valueLocation) { + // if we did not find it at that level then try removing any issues inthe property path + propertyPath = propertyPath.filter((v) => { + // remove any classnames + // like http://id.loc.gov/ontologies/bibframe/PlaybackCharacteristic + if (!v.propertyURI.match(/http:\/\/id\.loc\.gov\/ontologies\/bibframe\/[A-Z][a-z]+/)) { + return true + } else { + return false + } + }) + if (propertyPath.length > 0) { + valueLocation = utilsProfile.returnValueFromPropertyPath(pt, propertyPath) } - } } - // they changed something - this.dataChanged() - - // console.log("USERVALUE IS",userValue) - pt.userValue = userValue - } - }, + if (valueLocation) { + let values = [] + for (let v of valueLocation) { + let URI = null + let label = null - /** - * returns the structure of the component, used in the debug modal - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @return {array} - an array of objs representing the simple lookup values - */ - returnStructureByComponentGuid: function(componentGuid){ + if (v['@id']) { + URI = v['@id'] + } + for (let lP of LABEL_PREDICATES) { + if (v[lP] && v[lP][0][lP]) { + label = v[lP][0][lP] + break + } + } - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) + let uneditable = false + + // if it is deepHierarchy then then we are copy pasting what came into the system and they cann change it anyway. + if (pt.deepHierarchy) { uneditable = true } + + + if (URI && label) { + values.push({ + '@guid': v['@guid'], + URI: URI, + label: label, + needsDereference: false, + uneditable: uneditable, + isLiteral: false, + }) + } else if (URI && !label) { + values.push({ + '@guid': v['@guid'], + URI: URI, + label: label, + needsDereference: true, + uneditable: uneditable, + isLiteral: false, + }) + } else if (!URI && label) { + values.push({ + '@guid': v['@guid'], + URI: URI, + label: label, + needsDereference: false, + uneditable: uneditable, + isLiteral: true, + }) + } - return pt + } - }, + return values + } + // if valueLocation is false then it did not find anytihng meaning its empty, return empty array + return [] - /** - * return the XML of the active editing record - * + }, - * @return {string} - the XML string of output - */ - buildExportXML: function(){ - return utilsExport.buildXML(this.activeProfile) - }, + /** + * returns a complex lookup value of field + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value + * @return {array} - an array of objs representing the simple lookup values + */ + returnComplexLookupValueFromProfile: function (componentGuid, propertyPath) { + // TODO: reconcile this to how the profiles are built, or dont.. + // remove the sameAs from this property path, which will be the last one, we don't need it + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.w3.org/2002/07/owl#sameAs') }) + // TODOL reconcile this in the profiles (!!!) + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.loc.gov/mads/rdf/v1#componentList') }) + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.loc.gov/mads/rdf/v1#Topic') }) + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.loc.gov/mads/rdf/v1#Geographic') }) - downloadBFDotOrg: async function(format='marc'){ - let xml = await utilsExport.buildXML(this.activeProfile) - let marc = await utilsNetwork.marcPreview(xml.bf2Marc, false) - xml = xml.xmlStringFormatted - marc =marc[0].results.stdout - let use = marc - let filename = `${this.activeProfile.eId}.marcxml.xml` - if (format=='marc'){ - use = marc - }else{ - use = xml - filename = `${this.activeProfile.eId}.rdfxml.xml` - } + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + let valueLocation = utilsProfile.returnValueFromPropertyPath(pt, propertyPath) + let deepestLevelURI = propertyPath[propertyPath.length - 1].propertyURI - const blob = new Blob([use], { type: "text/xml" }); - const url = URL.createObjectURL(blob); + // console.log(pt.propertyURI) + // console.log("propertyPath=",propertyPath) - const a = document.createElement("a"); - a.href = url; - a.download = filename; + // working with Hubs as subjects breaks a few things. + // rewrite the property Path if we are working with them + if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject') { - // Append to body and trigger click (some browsers might require this for reliable click event) - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); // Clean up + if (pt.userValue && + pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type'] && + (pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/Hub" || pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/Work") + ) - URL.revokeObjectURL(url); + // it is a subject remove label properties + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== "http://www.loc.gov/mads/rdf/v1#authoritativeLabel") }) + valueLocation = utilsProfile.returnValueFromPropertyPath(pt, propertyPath) - }, + // console.log("NEW propertyPath=",propertyPath) - /** - * return the MARC transformation from the back end - * - - * @return {string} - the MARC string of output - */ - marcPreview: async function(){ - let xml = await utilsExport.buildXML(this.activeProfile) - let preview = null - if (!usePreferenceStore().returnValue('--b-edit-main-splitpane-opac-marc-html')){ - preview = await utilsNetwork.marcPreview(xml.bf2Marc, false) - } else { - preview = await utilsNetwork.marcPreview(xml.bf2Marc, true) - } - - // clean it up a bit for the component - let versions = preview.map((v)=>{ return v.version}).sort().reverse() - - let results = [] - - let newResults = [] - let selectedDefault = false - - - for (let v of versions){ - let toAdd = preview.filter((p) => { return (p.version == v) })[0] - if (toAdd.results && toAdd.results.stdout && selectedDefault == false){ - toAdd.default = true - selectedDefault = true - }else{ - toAdd.default = false - } + } - if (toAdd.results && !toAdd.results.stdout){ - toAdd.error = true - }else{ - toAdd.error = false - } - newResults.push(toAdd) - } + if (valueLocation) { + let values = [] + for (let v of valueLocation) { + let URI = null + let label = null - let defaultVer = newResults.filter((p) => { return (p.default == true) })[0] - if (defaultVer && defaultVer.default){ - defaultVer = defaultVer.version - }else{ - defaultVer = null - } - return({ - default: defaultVer, - versions: newResults, - }) + if (v['@id']) { + URI = v['@id'] + } + for (let lP of LABEL_PREDICATES) { + if (v[lP] && v[lP][0][lP]) { + label = v[lP][0][lP] + break + } + } - }, + // look for bf:title -> bf:mainTitle + if (!label) { + for (let lP1 of LABEL_PREDICATES) { + for (let lP2 of LABEL_PREDICATES) { + if (v[lP1] && v[lP1][0][lP2] && v[lP1][0][lP2][0][lP2]) { + label = v[lP1][0][lP2][0][lP2] + break + } + } + } + } + let source = null + if (URI && URI.indexOf('/fast/') > 1) { + source = 'FAST' + } + let uneditable = false + // if we don't have a URI for a work don't let them edit it + if (!URI && label && v['@type'] && v['@type'] == 'http://id.loc.gov/ontologies/bibframe/Work') { + uneditable = true + } + if (!URI && label && v['@type'] && v['@type'] == 'http://id.loc.gov/ontologies/bflc/Uncontrolled') { + uneditable = true + } + if (!URI && label && v['@type'] && v['@type'] == 'http://id.loc.gov/ontologies/bibframe/Uncontrolled') { + uneditable = true + } - /** - * Save the record to the Marva scratch-pad backend - * + // always allow editing subjects + if (pt && pt.propertyURI && pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/subject") { + uneditable = false + } - * @return {boolean} - did it save - */ - saveRecord: async function(){ - let xml = await utilsExport.buildXML(this.activeProfile) - let saved = false - if (!this.isTestEnv()){ //Don't try to save if in test env - saved = await utilsNetwork.saveRecord(xml.xlmStringBasic, this.activeProfile.eId) - } + // if it is deepHierarchy then then we are copy pasting what came into the system and they cann change it anyway. + if (pt.deepHierarchy) { uneditable = true } + + if (URI && label) { + values.push({ + '@guid': v['@guid'], + URI: URI, + label: label, + source: source, + needsDereference: false, + isLiteral: false, + uneditable: uneditable, + type: v['@type'] + }) + } else if (URI && !label) { + values.push({ + '@guid': v['@guid'], + URI: URI, + label: label, + source: source, + needsDereference: true, + isLiteral: false, + uneditable: uneditable, + type: v['@type'] + }) + } else if (!URI && label) { + + values.push({ + '@guid': v['@guid'], + URI: URI, + label: label, + source: source, + needsDereference: false, + uneditable: uneditable, + isLiteral: true, + type: v['@type'] + }) + } - if (saved){ - this.activeProfileSaved = true - this.logEvent('SAVED_RECORD') - } - }, + } - /** - * Log an event to the backend, extracting user/record info from current state - * @param {string} eventType - e.g. "LOAD_FROM_LCCN", "LOAD_FROM_COPYCAT", "CREATED_RECORD" - * @param {object} opts - optional overrides: eId, lccn, instanceId, metadata - */ - logEvent: async function(eventType, opts = {}){ - let prefStore = usePreferenceStore() - - // get the username from the SSO JWT payload - let username = null - if (prefStore.ssoUser){ - username = prefStore.ssoUser.username || prefStore.ssoUser.name || prefStore.ssoUser.email - } - if (!username){ - username = prefStore.catInitals - } - if (!username){ - console.warn('logEvent: no username available, skipping') - return false - } - - // pull eId and lccn from activeProfile if not provided - let eId = opts.eId || (this.activeProfile && this.activeProfile.eId) || null - let lccn = opts.lccn || null - let instanceId = opts.instanceId || null - - // try to extract lccn and instanceId from the record XML if available - if (!lccn || !instanceId){ - try { - let xml = await utilsExport.buildXML(this.activeProfile) - if (xml && xml.xlmStringBasic){ - let parser = new DOMParser() - let doc = parser.parseFromString(xml.xlmStringBasic, 'application/xml') - if (!lccn){ - let lccnEl = doc.getElementsByTagNameNS('http://id.loc.gov/ontologies/lclocal/', 'lccn')[0] - if (lccnEl){ - lccn = lccnEl.textContent - } - } - if (!instanceId){ - let extEls = doc.getElementsByTagNameNS('http://id.loc.gov/ontologies/lclocal/', 'externalid') - for (let el of extEls){ - if (el.textContent.includes('/instances/')){ - instanceId = el.textContent - break - } - } - } - } - } catch(e){ - console.warn('logEvent: could not extract XML metadata', e) - } - } + return values + } - let eventOpts = {} - if (eId) eventOpts.eId = eId - if (lccn) eventOpts.lccn = lccn - if (instanceId) eventOpts.instanceId = instanceId - if (opts.metadata) eventOpts.metadata = opts.metadata - return await utilsNetwork.logEvent(username, eventType, eventOpts) - }, + // if valueLocation is false then it did not find anytihng meaning its empty, return empty array + return [] - /** - * Ask for a record and parse it to load it into the editor - * - - * @return {boolean} - did it save - */ - loadRecord: async function(eid, profile){ - let xml = await utilsExport.buildXML(this.activeProfile) - // console.log("*****") - // console.log(xml) - let saved = await utilsNetwork.saveRecord(xml.xlmStringBasic, this.activeProfile.eId) - if (saved){ - this.activeProfileSaved = true - } - }, - /** - * Validate the reocrd - */ - validateRecord: async function(eid, profile){ - //console.log("Profile store: Validating?") - let xml = await utilsExport.buildXML(this.activeProfile) + }, - let response = await utilsNetwork.validate(xml.xlmStringBasic) - return response - }, + /** + * Sets a "Complex lookup" value, things from large lookups like NAF LCSH, etc + * this function only creates new values, does not modify (aka delete) + * @async + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} fieldGuid - the guid of the field + * @param {array} propertyPath - array of strings mapping the predicates to the blanknode for the value + * @param {string} URI - the URI for the value + * @param {string} label - the label to use + * @param {string} type - the URI of the type to use, like http://www.loc.gov/mads/rdf/v1#CorporateName + * @return {void} + */ + setValueComplex: async function (componentGuid, fieldGuid, propertyPath, URI, label, type, nodeMap = null, marcKey = null) { + // TODO: reconcile this to how the profiles are built, or dont.. + // remove the sameAs from this property path, which will be the last one, we don't need it + propertyPath = propertyPath.filter((v) => { return (v.propertyURI !== 'http://www.w3.org/2002/07/owl#sameAs') }) + // console.log("propertyPath=",propertyPath) + + let lastProperty = propertyPath.at(-1).propertyURI + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + + // if (!type && URI && !lastProperty.includes("intendedAudience")){ + // // I regretfully inform you we will need to look this up + // let context = await utilsNetwork.returnContext(URI) + // type = context.typeFull + // if (!marcKey){ + // marcKey = context.marcKey + // } + // } - /** - * Publish record to backend - * + if (['Work', 'Hub', 'GenreForm'].includes(type)) { + type = "http://id.loc.gov/ontologies/bibframe/" + type + } + if (type && !type.startsWith("http")) { + type = "http://www.loc.gov/mads/rdf/v1#" + type + } - * @return {obj} - response from posting action - */ - publishRecord: async function(eid, profile){ + // literals don't have a type or a URI & intendedAudience has extra considerations + // namely that the rdf:Type in BF is bf:Authority + if ((!type && !URI) || lastProperty.includes("intendedAudience")) { + type = await utilsRDF.suggestTypeProfile(lastProperty, pt) + if (type == false) { + type = await utilsRDF.suggestTypeNetwork(lastProperty) + } + } + // More granual typing for Geographic + if (type == "http://www.loc.gov/mads/rdf/v1#Geographic") { + if (propertyPath.filter((v) => { return v.propertyURI == 'http://id.loc.gov/ontologies/bibframe/geographicCoverage' }).length > 0) { + type = "http://id.loc.gov/ontologies/bibframe/GeographicCoverage" + } else { + type = "http://id.loc.gov/ontologies/bibframe/Place" + } + } + if (pt !== false) { + pt.hasData = true + pt.userModified = true + pt.dataLoaded = false - let postingHub = false - if (this.activeProfile && this.activeProfile.id && this.activeProfile.id.indexOf(':Hub')>-1){ - // ITS A HUB! - // do other things if its a hub - postingHub=true - } + // find the correct blank node to edit if possible, if we don't find it then we need to create it + let blankNode = utilsProfile.returnGuidLocation(pt.userValue, fieldGuid) - let xml = await utilsExport.buildXML(this.activeProfile) + if (blankNode === false) { + // create the path to the blank node + let buildBlankNodeResult = await utilsProfile.buildBlanknode(pt, propertyPath) - let pubResuts - let marva001 + pt = buildBlankNodeResult[0] - if (postingHub){ - pubResuts = await utilsNetwork.publish(xml.xlmStringBasic, this.activeProfile.eId, {id: 'Hub'}) - }else{ - pubResuts = await utilsNetwork.publish(xml.xlmStringBasic, this.activeProfile.eId, this.activeProfile) - } + // now we can make a link to the parent of where the literal value should live + blankNode = utilsProfile.returnGuidLocation(pt.userValue, buildBlankNodeResult[1]) - pubResuts.resourceLinks=[] - // if it was accepted by the system send it to the marva backend to store as posted + //empty out the blankNode's existing data so it only has the new data + for (let key of Object.keys(blankNode).filter((k) => !k.startsWith("@"))) { + blankNode[key] = [] + } - if (pubResuts.status){ - this.activeProfile.status = 'published' - await this.saveRecord() + // set the URI + // if its null then we are adding a literal + if (URI !== null) { + blankNode['@id'] = URI + } else { + // do nothing for now... + } - this.logEvent('PUBLISHED_RECORD') + // overwrite whatever the helper methods set the type to for this one, we know the final + // type and what it needs to be + // don't do this for subjects, Subjects as complexValues will add extra nesting that doesn't match a subject's XML + if (!propertyPath.some((pp) => pp.propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject')) { + blankNode['@type'] = type + } - const config = useConfigStore() - for (let rt in this.activeProfile.rt){ - let type = rt.split(':').slice(-1)[0] - // this.localMarva = false - let url = config.convertToRegionUrl(this.activeProfile.rt[rt].URI) - if (this.activeProfile.marvaLocalId){ - url = url.replace(this.activeProfile.eId, this.activeProfile.marvaLocalId) - } - let env = config.returnUrls.env + if (!Array.isArray(label)) { + label = [label] + } - // populate the title - if (type=='Instance'){ - let bibId = this.activeProfile.rt[rt].URI.split("/")[this.activeProfile.rt[rt].URI.split('/').length - 1] - document.title = `Marva | ${bibId}`; - } + for (let aLabelNode of label) { - pubResuts.resourceLinks.push({ - 'type':type, - 'url': url, - 'env': env - }) - } - } + if (!blankNode['http://www.w3.org/2000/01/rdf-schema#label']) { + blankNode['http://www.w3.org/2000/01/rdf-schema#label'] = [] + } + if (typeof aLabelNode == 'string') { + blankNode['http://www.w3.org/2000/01/rdf-schema#label'].push( + { + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': aLabelNode + } + ) + } else if (aLabelNode['@value']) { + let aNode = { + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': aLabelNode['@value'] + } + if (aLabelNode['@language']) { + aNode['@language'] = aLabelNode['@language'] + } + blankNode['http://www.w3.org/2000/01/rdf-schema#label'].push(aNode) - return pubResuts - }, + } else { + console.error("Cannot understand response from context extaction for label:", label) + } + } + // add in the venacular labels + if (nodeMap && nodeMap.vernacularLabels) { + for (let l of nodeMap.vernacularLabels) { + // make sure there really is a non-latin label + if (l.indexOf("@") == -1) { + continue + } + // the api returns it as "label@language" + let lLabel = l.split("@")[0] + let lLanguage = l.split("@")[1] + // make sure there is a label field for it + if (!blankNode['http://www.w3.org/2000/01/rdf-schema#label']) { + blankNode['http://www.w3.org/2000/01/rdf-schema#label'] = [] + } + blankNode['http://www.w3.org/2000/01/rdf-schema#label'].push( + { + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': lLabel, + '@language': lLanguage + } + ) + } + } + // if (URI.indexOf('n2010057779') > -1){ + // nodeMap.vernacularMarcKeys = ["4001 $a玄 武岩,$d1969-@zxx-Hani","4001 $a현 무암,$d1969-@zxx-Hang"] + // } - /** - * returns the label to use in bf code mode - * - * @param {object} structure - the structure value from the profile - * @param {boolean} title - return the title text not the short code - * @return {string} - the label - */ - returnBfCodeLabel: function(structure, returntitle, componentGuid){ + // console.log("nodeMap",nodeMap) - if (returntitle){ - let pt = utilsProfile.returnPt(this.activeProfile,structure['@guid']) - if (pt){ - return utilsExport.namespaceUri(pt.propertyURI) - }else{ - return utilsExport.namespaceUri(structure.propertyURI) - } + //Add gacs code to user data + if (nodeMap["gacs"]) { + blankNode["http://www.loc.gov/mads/rdf/v1#code"] = [] + for (let code in nodeMap["gacs"]) { + blankNode["http://www.loc.gov/mads/rdf/v1#code"].push( + { + '@guid': short.generate(), + "@gacs": "http://id.loc.gov/datatypes/codes/gac", + 'http://www.loc.gov/mads/rdf/v1#code': nodeMap["gacs"][code] + } + ) + } + } - } + if (!Array.isArray(marcKey)) { + marcKey = [marcKey] + } - let code = utilsParse.namespaceUri(structure.propertyURI) - // console.log(structure.propertyURI, code) + for (let aMarcKeyNode of marcKey) { - // HACK HACK HAVK - if (structure.propertyURI == 'http://www.loc.gov/mads/rdf/v1#Topic'){ - code = "bf:subject" - } + // console.log("aMarcKeyNode",aMarcKeyNode) - if (['rdfs:label', 'owl:sameAs'].includes(code)){ - // console.log(structure.propertyURI) - code = utilsParse.namespaceUri(this.rtLookup[structure.parentId].resourceURI) - } + if (!blankNode['http://id.loc.gov/ontologies/bflc/marcKey']) { + blankNode['http://id.loc.gov/ontologies/bflc/marcKey'] = [] + } + if (typeof aMarcKeyNode == 'string') { + blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push( + { + '@guid': short.generate(), + 'http://id.loc.gov/ontologies/bflc/marcKey': aMarcKeyNode + } + ) + } else if (aMarcKeyNode && aMarcKeyNode['@value']) { + let aNode = { + '@guid': short.generate(), + 'http://id.loc.gov/ontologies/bflc/marcKey': aMarcKeyNode['@value'] + } + if (aMarcKeyNode['@language']) { + aNode['@language'] = aMarcKeyNode['@language'] + } + blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push(aNode) + } else if (aMarcKeyNode && aMarcKeyNode.marcKey) { - let orgCode = code + let aNode = { + '@guid': short.generate(), + 'http://id.loc.gov/ontologies/bflc/marcKey': aMarcKeyNode.marcKey + } + if (aMarcKeyNode['@language']) { + aNode['@language'] = aMarcKeyNode['@language'] + } - code = code.replace('bflc:','lc:') + blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push(aNode) - // TODO - why are these classes? - // bf:Note becomes bf:note - if (code.charAt(3) === code.charAt(3).toUpperCase()){ - code = code.substring(0, 3) + code.charAt(3).toLowerCase() + code.substring(3 + 1); - } - // console.log("code=",code) + } else { + console.error("Cannot understand response from context extaction for marcKey:", marcKey) + } + } - // if we have it customized then just return that - if (shortCodesOverrides[code]){ - return shortCodesOverrides[code] - } + // add in the venacular marckeys + if (nodeMap && nodeMap.vernacularMarcKeys) { + for (let l of nodeMap.vernacularMarcKeys) { + // make sure there really is a non-latin label + if (l.indexOf("@") == -1 && l.indexOf("(bcp47)") == -1) { + continue + } + // the api returns it as "label@language" or (bcp47)language + let lLabel = '' + let lLanguage = '' - let justProperty = code.split(':')[1] + if (l.includes("@")) { + lLabel = l.split("@")[0] + lLanguage = l.split("@")[1] + } else if (l.includes("bcp47")) { + lLabel = l + lLanguage = l.split("(bcp47)")[1] + } - let numUpper = justProperty.length - justProperty.replace(/[A-Z]/g, '').length; + // make sure there is a label field for it + if (!blankNode['http://id.loc.gov/ontologies/bflc/marcKey']) { + blankNode['http://id.loc.gov/ontologies/bflc/marcKey'] = [] + } - if (numUpper == 2){ - code = code.split(':')[0] + ':' + justProperty.charAt(0) + justProperty.replace(/[a-z]/g, '') - }else if (numUpper == 1){ - code = code.split(':')[0] + ':' + justProperty.charAt(0) + justProperty.charAt(1) + justProperty.replace(/[a-z]/g, '') - }else if (numUpper == 0){ - code = code.split(':')[0] + ':' + justProperty.charAt(0) + justProperty.charAt(1) + justProperty.charAt(2) - } - // console.log("code=",code) + blankNode['http://id.loc.gov/ontologies/bflc/marcKey'].push( + { + '@guid': short.generate(), + 'http://id.loc.gov/ontologies/bflc/marcKey': lLabel, + '@language': lLanguage + } + ) + } - // if its just a rdf value then replace it with the acutal text - if (code == 'rdf:val'){ - let useLabel = structure.propertyLabel.split(' ')[0] - if (useLabel.length <= 6){ - code = useLabel.toLowerCase() - }else{ - code = `${useLabel.charAt(0)}${useLabel.charAt(1)}${useLabel.charAt(2)}${useLabel.charAt(3)}${useLabel.charAt(4)}${useLabel.charAt(5)}`.toLowerCase() - } - // console.log("HEY CODE IS",code) - } + } - // if (uri == '') - // chop off the namespace - if (code.includes(':')){ - code = code.split(':')[1] - } - // console.log('****!',orgCode, ',', (orgCode.includes('bf:')) ? `https://id.loc.gov/ontologies/bibframe.html#p_${orgCode.split(':')[1]}` : 'bflc or other', ',',code) - // console.log(code) - // console.log(structure.propertyURI) - // console.log(this.rtLookup[structure.parentId]) + // if (nodeMap["marcKey"]){ + // blankNode["http://id.loc.gov/ontologies/bflc/marcKey"] = [ + // { + // '@guid': short.generate(), + // 'http://id.loc.gov/ontologies/bflc/marcKey': nodeMap["marcKey"][0] + // } + // ] + // } - return code + // add the source for Genre/Form + if (propertyPath.map((obj) => obj.propertyURI).includes("http://id.loc.gov/ontologies/bibframe/genreForm")) { + let objId = blankNode['@id'] + if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/authorities/genreForms/collection_LCGFT_General')) { + blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ + { + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/Source", + "@id": "http://id.loc.gov/authorities/genreForms/collection_LCGFT_General", + "http://www.w3.org/2000/01/rdf-schema#label": [ + { + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress genre/form terms for library and archival materials" + } + ] + } + ] + } if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/vocabulary/rbms/collection_rbmscv')) { + blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ + { + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/Source", + "@id": "http://id.loc.gov/vocabulary/genreFormSchemes/rbmscv", + "http://www.w3.org/2000/01/rdf-schema#label": [ + { + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "RBMS Controlled Vocabularies" + } + ] + } + ] + } + } - }, + // add source for LCDGT + if (propertyPath.map((obj) => obj.propertyURI).includes("http://id.loc.gov/ontologies/bibframe/intendedAudience")) { + let objId = blankNode['@id'] + if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/authorities/demographicTerms/collection_LCDGT_General')) { + blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ + { + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/Source", + "@id": "http://id.loc.gov/authorities/demographicTerms/collection_LCDGT_General", + "http://www.w3.org/2000/01/rdf-schema#label": [ + { + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress demographic group term and code list" + } + ] + } + ] + } + if (nodeMap.collections && nodeMap.collections.includes("http://id.loc.gov/vocabulary/maudience/collection_maudience")) { + blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ + { + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/Source", + "@id": "http://id.loc.gov/vocabulary/maudience/collection_maudience", + "http://www.w3.org/2000/01/rdf-schema#label": [ + { + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "MARC Audience" + } + ] + } + ] + } + } + if (nodeMap.collections && nodeMap.collections.includes('http://id.loc.gov/authorities/subjects/collection_LCSHAuthorizedHeadings')) { + blankNode['http://id.loc.gov/ontologies/bibframe/source'] = [ + { + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/Source", + "@id": "http://id.loc.gov/authorities/subjects/collection_LCSHAuthorizedHeadings", + "http://www.w3.org/2000/01/rdf-schema#label": [ + { + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress subject headings" + } + ] + } + ] + } - /** - * returns if the request property is the "main" property of that component - * - * @param {string} componentGuid - the guid of the component - * @param {array} propertyPath - the property path of the property in question - * @return {boolean} - - */ - inlineIsMainProperty: function(componentGuid, fieldStructure, propertyPath){ - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - // let valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) - // some hard coded hacks - if (fieldStructure.propertyURI === 'http://id.loc.gov/ontologies/bflc/nonSortNum'){ - return false - } - if (fieldStructure.propertyURI === 'http://id.loc.gov/ontologies/bibframe/mainTitle'){ - return true - } + } else { + let parent = utilsProfile.returnGuidParent(pt.userValue, fieldGuid) + + // make sure we can find where to put the new one + if (parent[lastProperty]) { + // create a new node here + parent[lastProperty].push({ + '@id': URI, + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': [ + { + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': label + } + ] + }) + } else { + console.error("Could not find the parent[lastProperty] of the existing value", { 'parent': parent, 'pt.userValue': pt.userValue, 'fieldGuid': fieldGuid }) + } - // if it doesn't have any valuetemplates then it is a single property component - if (pt.valueConstraint.valueTemplateRefs.length==0){ - return true - } - // if the label matches the first label in one of the value templates then it is the first property and probably the most important? - for (let ref of pt.valueConstraint.valueTemplateRefs){ - if (this.rtLookup[ref].propertyTemplates[0].propertyLabel === fieldStructure.propertyLabel){ - return true - } - } + } + // they changed something - return false + this.dataChanged() - }, - /** - * returns if the request property has a value in it - * - * @param {string} componentGuid - the guid of the component - * @param {array} propertyPath - the property path of the property in question - * @return {boolean} - - */ - inlinePropertyHasValue: function(componentGuid, fieldStructure, propertyPath){ - - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - let valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) - if (valueLocation !== false){ - return true - } - return false - - }, - /** - * returns possible fields in that can be displayed in the component - * - * @return {array} - array of the fields - */ - returnPossibleFieldsInComponent: function(componentGuid){ - - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - if (pt.valueConstraint.valueTemplateRefs.length==0){ - - return [] - } - let use = [] + } else { + console.error('setValueComplex: Cannot locate the component by guid', componentGuid, this.activeProfile) + } - if (pt.valueConstraint.valueTemplateRefs.length===1){ - use = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates - }else{ + console.log("pt is ", JSON.stringify(pt.userValue)) + }, - if (pt.activeType){ + /** + * sets the uservalue when dealing with subject headings + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {array} subjectComponents - objects with the data to represent the component parts of subject heading + * @param {array} propertyPath - the path of uris to get to this level + * @return {void} - + */ + setValueSubject: async function (componentGuid, subjectComponents, propertyPath) { + // we're just going to overwrite the whole userValue with the constructed headings + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - for (let ref of pt.valueConstraint.valueTemplateRefs){ + // console.log('-----') + // console.log(pt) + // console.log(subjectComponents) + // console.log(propertyPath) - if (this.rtLookup[ref].resourceURI === pt.activeType){ + // find it + if (pt) { + // build out the hiearchy + let userValue = {} - use = this.rtLookup[ref].propertyTemplates - break - } - } - }else{ - // just use the first template - use = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates - } - } + // build the hiearchy if it doesn't exist to place the data + // we are adding the rdfs:label and the @id to this property + // we just need to know where to put it - // let codes = this.returnBfCodeLabel(pt) - let useReturn = [] - for (let p of use){ - useReturn.push({'code':this.returnBfCodeLabel(p), 'label' : p.propertyLabel, 'uri': p.propertyURI}) + // a reference to allow us to write to the end of the hierarchy + let currentUserValuePos = userValue - } - return useReturn + // used as a reference to the last postion's parent, so we can easily add a new sibling + // let currentUserValuePosParent = null - }, + // keeps track of the @type, will be the last @type of the hiearchy when done looping + let thisLevelType + let complexCheck = false + for (let p of propertyPath) { + // if the property is owl:sameAs it is the last field + // of where we are building the entitiy, so we don't want + // bf:Agent -> owl:sameAs we just want the bf:Agent with values filed in there + if (p.propertyURI == 'http://www.w3.org/2002/07/owl#sameAs') { + break + } + // same with component list, we'll build that manually + if (p.propertyURI == 'http://www.loc.gov/mads/rdf/v1#componentList') { + break + } + if (!currentUserValuePos[p.propertyURI]) { + currentUserValuePos[p.propertyURI] = [] + } - /** - * - * - * @return {array} - array of the fields - */ - setInlineDisplay: function(componentGuid, label){ + let thisLevelType = utilsRDF.suggestTypeProfile(p.propertyURI, pt) + if (thisLevelType === false) { + // did not find it in the profile, look to the network + thisLevelType = await utilsRDF.suggestTypeNetwork(p.propertyURI) + } - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - if (!pt.inlineModeDisplay){ - pt.inlineModeDisplay = {} - } + // if it's a complexSubject, replace bf:Topic with madsrdf:ComplexSubject -- the conversion expects this + if (thisLevelType == "http://id.loc.gov/ontologies/bibframe/Topic" && propertyPath.some((obj) => obj.propertyURI == "http://www.loc.gov/mads/rdf/v1#componentList")) { + // thisLevelType = 'madsrdf:ComplexSubject' + complexCheck = true + } - pt.inlineModeDisplay[label] = true + let thisLevel = { '@guid': short.generate() } + if (!utilsRDF.isUriALiteral(thisLevelType)) { + thisLevel['@type'] = thisLevelType + } + if (currentUserValuePos[p.propertyURI].length == 0) { + currentUserValuePos[p.propertyURI].push(thisLevel) + } + // currentUserValuePosParent = currentUserValuePos[p.propertyURI] + currentUserValuePos = currentUserValuePos[p.propertyURI][0] + } - }, + pt.hasData = true + pt.userModified = true + pt.dataLoaded = false - /** - * - * - * @return {array} - array of the fields - */ - inlineFieldIsToggledForDisplay: function(componentGuid, label){ + if (pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"] && + pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0] && + pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"] && + pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"][0]) { - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - if (!pt.inlineModeDisplay){ - return false - } + userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"] = JSON.parse(JSON.stringify(pt.userValue["http://id.loc.gov/ontologies/bibframe/subject"][0]["http://id.loc.gov/ontologies/bibframe/source"])) + } - if (pt.inlineModeDisplay[label]){ - return true - } + if (pt.userValue['@root']) { + userValue['@root'] = JSON.parse(JSON.stringify(pt.userValue['@root'])) + } + if (pt.userValue['@guid']) { + userValue['@guid'] = JSON.parse(JSON.stringify(pt.userValue['@guid'])) + } + currentUserValuePos["http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] = [{ + "@guid": short.generate(), + "@id": "http://id.loc.gov/authorities/subjects" + }] - }, + // if it is a solo subject heading + if (subjectComponents.length == 1) { + // it might be a literal. + if (subjectComponents[0].uri) { + currentUserValuePos['@id'] = subjectComponents[0].uri + } else { + delete currentUserValuePos["http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] + } - /** - * - * - * @return {boolean} - true or false if the pt passed will have a ref component drop down selection - */ - ptHasRefComponent: function(pt){ - - if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length > 1){ - return true - }else{ - - // if it only has 1 reference template, that one might have multuple - if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length == 1){ - // loop through all the pts and see if any of them have multi valuetemplates - // for (let subPt of this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates){ - // if (subPt.valueConstraint && subPt.valueConstraint.valueTemplateRefs && subPt.valueConstraint.valueTemplateRefs.length > 1){ - // return true - // } - // } - - // acutally just look at the first one, we don't really care if a sub pt has multi - let subPt = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates[0] - if (subPt.valueConstraint && subPt.valueConstraint.valueTemplateRefs && subPt.valueConstraint.valueTemplateRefs.length > 1){ - return true - } - } - } - return false - }, + // it might be a Hub if so its not a isMemberOfMADSScheme subject + if (subjectComponents[0].type && subjectComponents[0].type.toLowerCase().indexOf("hub") > -1) { + delete currentUserValuePos["http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme"] + } + if (subjectComponents[0].type) { + currentUserValuePos['@type'] = subjectComponents[0].type.replace('madsrdf:', 'http://www.loc.gov/mads/rdf/v1#') + } else { + currentUserValuePos['@type'] = 'madsrdf:Topic' + } - /** - * Flips the canBeHidden flag on a property and reordereds it to the the end of the property list - * In adhoc mode this makes it look like its being added to the profile - * @return {void} - - */ - setPropertyVisible: function(profile,component){ + // if there is a URI add authorized label + if (currentUserValuePos['@id']) { - this.activeProfile.rt[profile].pt[component].canBeHidden = false + currentUserValuePos["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"] = [{ + "@guid": short.generate(), + "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": subjectComponents[0].label + }] + } + currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#label"] = [{ + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": subjectComponents[0].label + }] + if (subjectComponents[0].marcKey) { + currentUserValuePos["http://id.loc.gov/ontologies/bflc/marcKey"] = [{ + "@guid": short.generate(), + "http://id.loc.gov/ontologies/bflc/marcKey": subjectComponents[0].marcKey + }] + } - this.activeProfile.rt[profile].ptOrder.push(this.activeProfile.rt[profile].ptOrder.splice(this.activeProfile.rt[profile].ptOrder.indexOf(component), 1)[0]); + // add in the non latin if present + if (subjectComponents[0].nonLatinLabel && subjectComponents[0].nonLatinLabel.length > 0) { + for (let nlL of subjectComponents[0].nonLatinLabel) { + currentUserValuePos["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"].push( + { + "@guid": short.generate(), + "@language": nlL['@language'], + "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": nlL['@value'] + } + ) + currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#label"].push( + { + "@guid": short.generate(), + "@language": nlL['@language'], + "http://www.w3.org/2000/01/rdf-schema#label": nlL['@value'] + } + ) + } + } - }, + if (subjectComponents[0].nonLatinMarcKey && subjectComponents[0].nonLatinMarcKey.length > 0) { + for (let nlMK of subjectComponents[0].nonLatinMarcKey) { + currentUserValuePos["http://id.loc.gov/ontologies/bflc/marcKey"].push( + { + "@guid": short.generate(), + "@language": nlMK['@language'], + "http://id.loc.gov/ontologies/bflc/marcKey": nlMK['@value'] + } + ) + } + } - /** - * Loads the record from marva backend by E id - * - * @return {void} - - */ - loadRecordFromBackend: async function(eid){ - // loading a fresh record into the editor, clear the "posted" UI flag so the - // post button doesn't stay green from a previously posted record - this.activeProfilePosted = false - this.activeProfilePostedTimestamp = false - this.activeProfile = await utilsProfile.loadRecordFromBackend(eid) - }, - /** - * Pass the component GUID to use and it will insert a MLC number into the userValue - * based on the dimensions in the instance otherwise it will ask the user to give the size - **/ - insertMLCNumber: async function(componentGuid){ - - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - let enhanceResults = await this.mlcNumberEnhance() - - // console.log("enhance results",enhanceResults) - // look for the dimensions in the instance - let dimensions = null - for (let rtId in this.activeProfile.rt){ - if (this.activeProfile.rt[rtId].URI && this.activeProfile.rt[rtId].URI.indexOf('/instances/')>-1){ - let instancePt = this.activeProfile.rt[rtId].pt - for (let ptId in instancePt){ - if (instancePt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/dimensions' && instancePt[ptId].userValue && instancePt[ptId].userValue['http://id.loc.gov/ontologies/bibframe/dimensions'] && instancePt[ptId].userValue['http://id.loc.gov/ontologies/bibframe/dimensions'][0]['http://id.loc.gov/ontologies/bibframe/dimensions']){ - dimensions = instancePt[ptId].userValue['http://id.loc.gov/ontologies/bibframe/dimensions'][0]['http://id.loc.gov/ontologies/bibframe/dimensions'] - break - } - } - if (dimensions){ - break - } - } - } - if (dimensions){ - let size = parseDimensions(dimensions) - // if it wasnt able to parse it unset it - if (!size || !size.size){ - dimensions = null - }else{ - dimensions = size.size - } - } - - if (!dimensions){ - dimensions = prompt("Could not find dimensions in the record. Please enter the MLC size to use: S, M, L or F") - // check they did it right - if (dimensions && ['S','M','L','F'].includes(dimensions.toUpperCase())){ - dimensions = dimensions.toUpperCase() - }else{ - alert("Invalid size entered. Try inserting MLC again and enter S, M, L or F. ") - return - } - }else{ - // it did parse scuessfully convert the lib respomse into the size letter - if (dimensions== 'small'){ - dimensions = 'S' - }else if (dimensions == 'medium'){ - dimensions = 'M' - }else if (dimensions == 'large'){ - dimensions = 'L' - }else if (dimensions == 'oversize' || dimensions == 'folio'){ - dimensions = 'F' - }else{ - alert("Error in parsing dimensions. ", dimensions) - return - } - } - - // now ask the API for the next number - let number = await utilsNetwork.getMLCNumber(dimensions.toLowerCase()) - this.logEvent('REQUESTED_MLC_NUMBER', { metadata: [number] }) - - let division = usePreferenceStore().returnValue('--b-shelflist-mlc-division') - if (division && division != ""){ - // they have used a divsion before or set it so insert the value in the correct place in the MLC number - number = number.slice(0, 4) + division + number.slice(4) - } - - if (enhanceResults.useP){ - number = number + ' (P)' - }else if (enhanceResults.useLCC){ - number = number + ` (${enhanceResults.useLCC})` - } - - - // now update the userValue of the pt with that number - // it goes into the userValue -> http://id.loc.gov/ontologies/bibframe/classification[0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] - if (!pt.userValue){ - pt.userValue = {} - } - let dataFieldGuid = short.generate() - if (!pt.userValue['http://id.loc.gov/ontologies/bibframe/classification']){ - pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'] = [{ - "@guid": short.generate(), - "@type": "http://id.loc.gov/ontologies/bibframe/ClassificationLcc", - "http://id.loc.gov/ontologies/bibframe/assigner": [{ - "@guid": short.generate(), - "@id": "http://id.loc.gov/vocabulary/organizations/dlc", - "@type": "http://id.loc.gov/ontologies/bibframe/Agent", - "http://www.w3.org/2000/01/rdf-schema#label": [{ - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "United States, Library of Congress" - }] - }], - "http://id.loc.gov/ontologies/bibframe/classificationPortion": [{ - "@guid": dataFieldGuid, - "http://id.loc.gov/ontologies/bibframe/classificationPortion": number - }], - "http://id.loc.gov/ontologies/bibframe/status": [{ - "@guid": short.generate(), - "@id": "http://id.loc.gov/vocabulary/mstatus/uba", - "@type": "http://id.loc.gov/ontologies/bibframe/Status", - "http://www.w3.org/2000/01/rdf-schema#label": [{ - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "used by assigner" - }] - }] - }] - }else{ - let classEntry = pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] - if (!classEntry['@guid']) classEntry['@guid'] = short.generate() - if (!classEntry['@type']) classEntry['@type'] = "http://id.loc.gov/ontologies/bibframe/ClassificationLcc" - if (!classEntry['http://id.loc.gov/ontologies/bibframe/assigner']){ - classEntry['http://id.loc.gov/ontologies/bibframe/assigner'] = [{ - "@guid": short.generate(), - "@id": "http://id.loc.gov/vocabulary/organizations/dlc", - "@type": "http://id.loc.gov/ontologies/bibframe/Agent", - "http://www.w3.org/2000/01/rdf-schema#label": [{ - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "United States, Library of Congress" - }] - }] - } - if (!classEntry['http://id.loc.gov/ontologies/bibframe/status']){ - classEntry['http://id.loc.gov/ontologies/bibframe/status'] = [{ - "@guid": short.generate(), - "@id": "http://id.loc.gov/vocabulary/mstatus/uba", - "@type": "http://id.loc.gov/ontologies/bibframe/Status", - "http://www.w3.org/2000/01/rdf-schema#label": [{ - "@guid": short.generate(), - "http://www.w3.org/2000/01/rdf-schema#label": "used by assigner" - }] - }] - } - if (!classEntry['http://id.loc.gov/ontologies/bibframe/classificationPortion']){ - classEntry['http://id.loc.gov/ontologies/bibframe/classificationPortion'] = [{ - "@guid": dataFieldGuid, - "http://id.loc.gov/ontologies/bibframe/classificationPortion": number - }] - }else{ - classEntry['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0] = { - "@guid": dataFieldGuid, - "http://id.loc.gov/ontologies/bibframe/classificationPortion": number - } - } - } - - // remove any empty classification entries (cloned but never populated) - for (let rtId in this.activeProfile.rt){ - for (let ptId in this.activeProfile.rt[rtId].pt){ - let ptObj = this.activeProfile.rt[rtId].pt[ptId] - if (ptObj.propertyURI === 'http://id.loc.gov/ontologies/bibframe/classification' && ptObj.userValue){ - if (!ptObj.userValue['http://id.loc.gov/ontologies/bibframe/classification']){ - // userValue only has @root, nothing else — mark as deleted - ptObj.deleted = true - }else{ - let classArr = ptObj.userValue['http://id.loc.gov/ontologies/bibframe/classification'] - for (let i = classArr.length - 1; i >= 0; i--){ - if (!classArr[i]['http://id.loc.gov/ontologies/bibframe/classificationPortion']){ - classArr.splice(i, 1) - } - } - } - } - } - } + // store.state.activeUndoLog.push(`Added subject heading ${subjectComponents[0].label}`) + } else if (subjectComponents.length > 1) { + //userValue - this.dataChanged() - return dataFieldGuid + let fullLabel = subjectComponents.map((c) => { return c.label }).join('--') + // store.state.activeUndoLog.push(`Added subject heading ${fullLabel}`) - }, + currentUserValuePos["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"] = [{ + "@guid": short.generate(), + "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": fullLabel + }] + currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#label"] = [{ + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": fullLabel + }] + if (complexCheck) { + currentUserValuePos["http://www.w3.org/2000/01/rdf-schema#type"] = [{ + "@guid": short.generate(), + "@id": "http://www.loc.gov/mads/rdf/v1#ComplexSubject" + }] + } - /** - * If it is a LCC component info about the LCC numbers - * used in the interface rendering - * @return {boolean} - */ - returnLccInfo: function(componentGuid){ - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) + // we need to make the component list then - // if it is empty and brand new dont do the next check it wont have any data - if (Object.keys(pt.userValue).length > 1){ // this means it doesn't only have @root in the userValue and has data populated - // maybe it it is a dewy or other classifciation, only proceed if it is LCC - if (pt.userValue?.['http://id.loc.gov/ontologies/bibframe/classification']?.[0]?.['@type'] !== 'http://id.loc.gov/ontologies/bibframe/ClassificationLcc'){ - // console.log("RETURN FALSE 1") + currentUserValuePos["http://www.loc.gov/mads/rdf/v1#componentList"] = [] - return false + for (let c of subjectComponents) { + let compo = { + "@guid": short.generate(), + "@type": c.type.replace('madsrdf:', 'http://www.loc.gov/mads/rdf/v1#'), + "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": [{ + "@guid": short.generate(), + "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": c.label + }] + } - } + if (c.uri) { + compo['@id'] = c.uri + } - } + if (c.marcKey) { + compo['http://id.loc.gov/ontologies/bflc/marcKey'] = [{ + "@guid": short.generate(), + 'http://id.loc.gov/ontologies/bflc/marcKey': c.marcKey + }] + if (c.nonLatinMarcKey && c.nonLatinMarcKey.length > 0) { + for (let nlMK of c.nonLatinMarcKey) { + compo["http://id.loc.gov/ontologies/bflc/marcKey"].push( + { + "@guid": short.generate(), + "@language": nlMK['@language'], + "http://id.loc.gov/ontologies/bflc/marcKey": nlMK['@value'] + } + ) + } + } + } - let classNumber = null - let classGuid = null + if (c.nonLatinLabel && c.nonLatinLabel.length > 0) { + for (let nlL of c.nonLatinLabel) { + compo["http://www.loc.gov/mads/rdf/v1#authoritativeLabel"].push( + { + "@guid": short.generate(), + "@language": nlL['@language'], + "http://www.loc.gov/mads/rdf/v1#authoritativeLabel": nlL['@value'] + } + ) + } + } - let cutterNumber = null - let cutterGuid = null + currentUserValuePos["http://www.loc.gov/mads/rdf/v1#componentList"].push(compo) - let work = null - let inst = null - let title = null - let titleNonSort = null - let firstSubject = null - let secondSubject = null - let contributors = [] - let subtitle = null - let titleNonLatin = null + } + } - // find the work and pull out stuff - for (let rtId in this.activeProfile.rt){ - let target = false + // did they add a LCSH heading, if so add that automatically as a source + for (let h of subjectComponents) { + if (h['uri'] && h['uri'].indexOf('id.loc.gov/authorities/subjects') > -1) { + if (!currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source']) { - if (rtId.indexOf(":Work") > -1){ - for (let ptId in this.activeProfile.rt[rtId].pt){ - if (this.activeProfile.rt[rtId].pt[ptId]['@guid'] == componentGuid){ - work = this.activeProfile.rt[rtId] - break - } - } - if (work){ break } - } - } - // console.log("work",work) - if (work){ - for (let ptId of work.ptOrder){ - let pt = JSON.parse(JSON.stringify(work.pt[ptId])) - /* - // - { - "@root": "http://id.loc.gov/ontologies/bibframe/title", - "http://id.loc.gov/ontologies/bibframe/title": [ - { - "@guid": "dYC2wKS5RkmPcP6rB7DH8m", - "@type": "http://id.loc.gov/ontologies/bibframe/Title", - "http://id.loc.gov/ontologies/bibframe/mainTitle": [ - { - "@guid": "th7wNkaXeBQxj4Gqc3PZKH", - "http://id.loc.gov/ontologies/bibframe/mainTitle": "Ch'ŏnggyech'ŏn Taehakch'ŏn ch'aekpang kŏri" - }, - { - "@guid": "49g8LszgTg4d6GzN7fBFhK", - "http://id.loc.gov/ontologies/bibframe/mainTitle": "청계천 대학천 책방 거리", - "@language": "ko-hang" + currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source'] = [ + { + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/Source", + "@id": "http://id.loc.gov/vocabulary/subjectSchemes/lcsh", + "http://www.w3.org/2000/01/rdf-schema#label": [ + { + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress subject headings" + } + ] + } + ] } - ] - } - ] - } - */ + break + } else if (h['uri'] && h['uri'].indexOf('id.loc.gov/authorities/names') > -1) { - if (pt && pt.propertyURI=='http://id.loc.gov/ontologies/bibframe/title'){ - let titleUserValue = pt.userValue - if (titleUserValue && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'] && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'].length>0 && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0]){ - titleUserValue = titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0] - if (titleUserValue && titleUserValue["@type"]=="http://id.loc.gov/ontologies/bibframe/Title" && titleUserValue['http://id.loc.gov/ontologies/bibframe/mainTitle']){ + if (!currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source']) { - let titleBnodeSorted = titleUserValue + currentUserValuePos['http://id.loc.gov/ontologies/bibframe/source'] = [ + { + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/Source", + // "@id": "http://id.loc.gov/vocabulary/subjectSchemes/naf", + "@id": "http://id.loc.gov/vocabulary/subjectSchemes/lcsh", + "http://www.w3.org/2000/01/rdf-schema#label": [ + { + "@guid": short.generate(), + // "http://www.w3.org/2000/01/rdf-schema#label": "NACO authority file" + "http://www.w3.org/2000/01/rdf-schema#label": "Library of Congress subject headings" + + } + ] + } + ] + } + break + } - if (titleUserValue['http://id.loc.gov/ontologies/bibframe/mainTitle'].length > 1){ - // there are multiple titles, we want to make sure to pick the latin one - titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'] = this.sortObjectsByLatinMatch(titleUserValue['http://id.loc.gov/ontologies/bibframe/mainTitle'],'http://id.loc.gov/ontologies/bibframe/mainTitle') } - if (titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'].length > 0 && titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'][0] && titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle']){ - title = titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] - // there could be a non-latin title - if (titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'].length > 1){ - for (let temp of titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle']){ - if (temp["@language"]){ - let lang = temp["@language"] - if (!lang.includes("-latin") && lang != "en"){ - titleNonLatin = temp["http://id.loc.gov/ontologies/bibframe/mainTitle"] + // double check the @type of the resource we want to be as specific as possible + if (currentUserValuePos['@type'] == 'http://www.w3.org/2000/01/rdf-schema#Resource') { + // this is very generic, try not to use that + if (pt.activeType) { + currentUserValuePos['@type'] = pt.activeType + } else { + if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length > 0) { + let useTemplate = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]] + if (useTemplate && useTemplate.resourceURI) { + currentUserValuePos['@type'] = useTemplate.resourceURI + } } - } } - } } - } - if (titleUserValue && titleUserValue["@type"]=="http://id.loc.gov/ontologies/bibframe/Title" && titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum']){ - if (titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'].length > 0 && titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'][0] && titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'][0]['http://id.loc.gov/ontologies/bflc/nonSortNum']){ - titleNonSort = titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'][0]['http://id.loc.gov/ontologies/bflc/nonSortNum'] - } - } - } - } + // they changed something + this.dataChanged() + // console.log("USERVALUE IS",userValue) + pt.userValue = userValue + } + }, - if (pt && pt.propertyURI=='http://id.loc.gov/ontologies/bibframe/contribution'){ - let contributorUserValue = pt.userValue - let type="normal" - if (contributorUserValue && contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'] && - contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'].length > 0 && - contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'][0] && - contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['@type'] ) - { - - contributorUserValue = contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'][0] - if (contributorUserValue && contributorUserValue["@type"] == "http://id.loc.gov/ontologies/bibframe/PrimaryContribution") - { - type="PrimaryContribution" - if (contributorUserValue['http://id.loc.gov/ontologies/bibframe/agent'] && - contributorUserValue['http://id.loc.gov/ontologies/bibframe/agent'][0]) - { - let agent = contributorUserValue['http://id.loc.gov/ontologies/bibframe/agent'][0] - if (agent && agent['http://www.w3.org/2000/01/rdf-schema#label'] && agent['http://www.w3.org/2000/01/rdf-schema#label'].length > 0 && agent['http://www.w3.org/2000/01/rdf-schema#label'][0] && agent['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']){ - // console.log("agentagentagentagent",agent) - let agentData = {type:type,label:agent['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']} - if (agent['@id']){ - agentData['@id'] = agent['@id'] - }else{ - agentData['@id'] = null - } - if (agent['@type']){ - agentData['@type'] = agent['@type'] - }else{ - agentData['@type'] = null - } - if (agent['http://id.loc.gov/ontologies/bflc/marcKey']){ - agentData['http://id.loc.gov/ontologies/bflc/marcKey'] = agent['http://id.loc.gov/ontologies/bflc/marcKey'] - }else{ - agentData['http://id.loc.gov/ontologies/bflc/marcKey'] = null - } - contributors.push(agentData) - } - } - } - } - } + /** + * returns the structure of the component, used in the debug modal + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @return {array} - an array of objs representing the simple lookup values + */ + returnStructureByComponentGuid: function (componentGuid) { - if (pt && pt.propertyURI=='http://id.loc.gov/ontologies/bibframe/subject' && (firstSubject === null || secondSubject === null) && !pt.deleted){ - let subjectUserValue = pt.userValue - if (subjectUserValue && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'].length > 0 && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0] && (subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'] || subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'])){ - if (subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'].length>0 && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']){ - if (firstSubject === null){ - firstSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] - } else { - secondSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] - } - } else if (subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'].length>0 && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']){ - if (firstSubject === null){ - firstSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] - } else { - secondSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] - } - } - } - } - } - } - // look at the instance to get the subtitle - for (let rtId in this.activeProfile.rt){ - if (rtId.indexOf(":Instance") > -1){ - inst = this.activeProfile.rt[rtId] - if (inst){ break } - } - } + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - if (inst){ - for (let ptId of inst.ptOrder){ - let pt = JSON.parse(JSON.stringify(inst.pt[ptId])) + return pt - if (pt && pt.propertyURI=='http://id.loc.gov/ontologies/bibframe/title'){ - let titleUserValue = pt.userValue + }, - if (titleUserValue && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'] && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'].length>0 && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0]){ - titleUserValue = titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0] - if (titleUserValue && titleUserValue["@type"]=="http://id.loc.gov/ontologies/bibframe/Title" && titleUserValue['http://id.loc.gov/ontologies/bibframe/subtitle']){ - let sub = titleUserValue['http://id.loc.gov/ontologies/bibframe/subtitle'] - subtitle = sub[0]["http://id.loc.gov/ontologies/bibframe/subtitle"] - } - } - } - } - } - if (subtitle && title){ - title = title.replace(": " + subtitle, "").trim() - } - if (pt && pt.userValue && pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'] && pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'].length>0){ - let uv = pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] + /** + * return the XML of the active editing record + * - if (uv && uv['@type'] && uv['@type'] == 'http://id.loc.gov/ontologies/bibframe/ClassificationLcc'){ + * @return {string} - the XML string of output + */ + buildExportXML: function () { + return utilsExport.buildXML(this.activeProfile) + }, - // this is a LCC field then - // we need to gather info from the component and the rest of the work to build links/suggestions + downloadBFDotOrg: async function (format = 'marc') { + let xml = await utilsExport.buildXML(this.activeProfile) + let marc = await utilsNetwork.marcPreview(xml.bf2Marc, false) + xml = xml.xmlStringFormatted + marc = marc[0].results.stdout + let use = marc + let filename = `${this.activeProfile.eId}.marcxml.xml` + if (format == 'marc') { + use = marc + } else { + use = xml + filename = `${this.activeProfile.eId}.rdfxml.xml` + } - if (uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'] && uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'].length>0 && uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0] && uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion']){ - classNumber = uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] - classGuid = uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['@guid'] + const blob = new Blob([use], { type: "text/xml" }); + const url = URL.createObjectURL(blob); - } - if (uv['http://id.loc.gov/ontologies/bibframe/itemPortion'] && uv['http://id.loc.gov/ontologies/bibframe/itemPortion'].length>0 && uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0] && uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion']){ - cutterNumber = uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion'] - cutterGuid = uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['@guid'] + const a = document.createElement("a"); + a.href = url; + a.download = filename; - } + // Append to body and trigger click (some browsers might require this for reliable click event) + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); // Clean up + URL.revokeObjectURL(url); + }, + /** + * return the MARC transformation from the back end + * - if (titleNonSort && titleNonSort.trim().length >0 && title){ - if (isNaN(parseInt(titleNonSort)) == false ){ - titleNonSort = parseInt(titleNonSort) - title = title.substr(titleNonSort).trim() + * @return {string} - the MARC string of output + */ + marcPreview: async function () { + let xml = await utilsExport.buildXML(this.activeProfile) + let preview = null + if (!usePreferenceStore().returnValue('--b-edit-main-splitpane-opac-marc-html')) { + preview = await utilsNetwork.marcPreview(xml.bf2Marc, false) + } else { + preview = await utilsNetwork.marcPreview(xml.bf2Marc, true) } - } - // console.log('classNumber',classNumber) - // console.log('cutterNumber',cutterNumber) - // console.log('work',work) - // console.log('title',title) - // console.log('titleNonSort',titleNonSort) - // console.log("contributors",contributors) + // clean it up a bit for the component + let versions = preview.map((v) => { return v.version }).sort().reverse() - return { - title: title, - subtitle: subtitle, - titleNonLatin: titleNonLatin, - classNumber:classNumber, - cutterNumber:cutterNumber, - titleNonSort:titleNonSort, - contributors:contributors, - firstSubject:firstSubject, - secondSubject:secondSubject, - cutterGuid:cutterGuid, - classGuid:classGuid - } + let results = [] - } else { - // This is a LCC field, it shouldn't return `false`. False causes things to disappear - // console.log("RETRUN FA:LSE 2") - return { - title: null, - subtitle: null, - titleNonLatin: null, - classNumber:null, - cutterNumber:null, - titleNonSort:null, - contributors:[], - firstSubject:null, - secondSubject:null, - cutterGuid:null, - classGuid:null - } - } + let newResults = [] + let selectedDefault = false - }else{ - if (pt && (pt.userValue && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/classification' || pt.userValue && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/relation') && Object.keys(pt.userValue).length == 1){ - - if (titleNonSort && titleNonSort.trim().length >0 && title){ - if (isNaN(parseInt(titleNonSort)) == false ){ - titleNonSort = parseInt(titleNonSort) - title = title.substr(titleNonSort).trim() - } - } - - // it is a new record, so there is no info but the LCC classification is by default so populate the other stuff - return { - title: title, - subtitle: subtitle, - titleNonLatin: titleNonLatin, - classNumber:null, - cutterNumber:null, - titleNonSort:titleNonSort, - contributors:contributors, - firstSubject:firstSubject, - secondSubject:secondSubject, - cutterGuid:null, - classGuid:null - } - - - } else if (pt && (pt.userValue && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/expressionOf')){ - return { - title: title, - subtitle: subtitle, - titleNonLatin: titleNonLatin, - classNumber:null, - cutterNumber:null, - titleNonSort:null, - contributors:contributors, - firstSubject:null, - secondSubject:null, - cutterGuid:null, - classGuid:null - } - } + for (let v of versions) { + let toAdd = preview.filter((p) => { return (p.version == v) })[0] + if (toAdd.results && toAdd.results.stdout && selectedDefault == false) { + toAdd.default = true + selectedDefault = true + } else { + toAdd.default = false + } + if (toAdd.results && !toAdd.results.stdout) { + toAdd.error = true + } else { + toAdd.error = false + } + newResults.push(toAdd) + } + let defaultVer = newResults.filter((p) => { return (p.default == true) })[0] + if (defaultVer && defaultVer.default) { + defaultVer = defaultVer.version + } else { + defaultVer = null + } + return ({ + default: defaultVer, + versions: newResults, + }) - } + }, - //ClassificationLcc - // console.log("RETRUN FA:LSE 3") - return false - }, - /** - * Walks the active profile and extracts subject and genreForm labels/URIs, - * then looks up classification URIs for each subject - * @return {object} - {subjects: [], genreForms: []} - */ - mlcNumberEnhance: async function(){ - - let subjects = [] - let genreForms = [] - - for (let rtId in this.activeProfile.rt){ - if (rtId.indexOf(":Work") > -1){ - for (let ptId of this.activeProfile.rt[rtId].ptOrder){ - let pt = this.activeProfile.rt[rtId].pt[ptId] - - // collect subjects - if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject' && !pt.deleted){ - let uv = pt.userValue - if (uv && uv['http://id.loc.gov/ontologies/bibframe/subject'] && uv['http://id.loc.gov/ontologies/bibframe/subject'].length > 0 && uv['http://id.loc.gov/ontologies/bibframe/subject'][0]){ - let subj = uv['http://id.loc.gov/ontologies/bibframe/subject'][0] - let label = null - let uri = null - if (subj['http://www.w3.org/2000/01/rdf-schema#label'] && subj['http://www.w3.org/2000/01/rdf-schema#label'].length > 0 && subj['http://www.w3.org/2000/01/rdf-schema#label'][0] && subj['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']){ - label = subj['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] - } else if (subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] && subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'].length > 0 && subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0] && subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']){ - label = subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] - } - // get URI: top-level @id, or first componentList entry's @id - if (subj['@id']){ - uri = subj['@id'] - } else if (subj['http://www.loc.gov/mads/rdf/v1#componentList'] && subj['http://www.loc.gov/mads/rdf/v1#componentList'].length > 0 && subj['http://www.loc.gov/mads/rdf/v1#componentList'][0] && subj['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id']){ - uri = subj['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id'] - } - if (label){ - subjects.push({label: label, uri: uri}) - } - } - } + /** + * Save the record to the Marva scratch-pad backend + * - // collect genreForms - if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/genreForm' && !pt.deleted){ - let uv = pt.userValue - if (uv && uv['http://id.loc.gov/ontologies/bibframe/genreForm'] && uv['http://id.loc.gov/ontologies/bibframe/genreForm'].length > 0 && uv['http://id.loc.gov/ontologies/bibframe/genreForm'][0]){ - let gf = uv['http://id.loc.gov/ontologies/bibframe/genreForm'][0] - let label = null - let uri = null - if (gf['http://www.w3.org/2000/01/rdf-schema#label'] && gf['http://www.w3.org/2000/01/rdf-schema#label'].length > 0 && gf['http://www.w3.org/2000/01/rdf-schema#label'][0] && gf['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']){ - label = gf['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] - } else if (gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] && gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'].length > 0 && gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0] && gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']){ - label = gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] - } - // get URI: top-level @id, or first componentList entry's @id - if (gf['@id']){ - uri = gf['@id'] - } else if (gf['http://www.loc.gov/mads/rdf/v1#componentList'] && gf['http://www.loc.gov/mads/rdf/v1#componentList'].length > 0 && gf['http://www.loc.gov/mads/rdf/v1#componentList'][0] && gf['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id']){ - uri = gf['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id'] - } - if (label){ - genreForms.push({label: label, uri: uri}) - } - } + * @return {boolean} - did it save + */ + saveRecord: async function () { + let xml = await utilsExport.buildXML(this.activeProfile) + let saved = false + if (!this.isTestEnv()) { //Don't try to save if in test env + saved = await utilsNetwork.saveRecord(xml.xlmStringBasic, this.activeProfile.eId) } - } - } - } - - // look up classification for the first subject only - let useLCC = null - if (subjects.length > 0 && subjects[0].uri){ - let classification = await utilsNetwork.getSubjectClassification(subjects[0].uri) - if (classification && classification.length > 0){ - useLCC = classification.charAt(0) - } - } - - // determine P-class from genreForms - // URIs that indicate useP = true (fiction-related) - let pClassTrueURIs = [ - 'http://id.loc.gov/authorities/genreForms/gf2014026339', - 'http://id.loc.gov/authorities/genreForms/gf2014026243', - 'http://id.loc.gov/authorities/genreForms/gf2014026259', - 'http://id.loc.gov/authorities/genreForms/gf2018026099', - 'http://id.loc.gov/authorities/genreForms/gf2015026019', - 'http://id.loc.gov/authorities/genreForms/gf2014026456', - 'http://id.loc.gov/authorities/genreForms/gf2015026020', - 'http://id.loc.gov/authorities/genreForms/gf2014026518', - 'http://id.loc.gov/authorities/genreForms/gf2014026542', - 'http://id.loc.gov/authorities/genreForms/gf2014026559' - ] - // URIs that map to specific letter codes (also useP = true) - let pClassLetterURIs = { - 'http://id.loc.gov/authorities/genreForms/gf2014026297': 'd', - 'http://id.loc.gov/authorities/genreForms/gf2014026094': 'e', - 'http://id.loc.gov/authorities/genreForms/gf2015026020': 'f', - 'http://id.loc.gov/authorities/genreForms/gf2014026110': 'h', - 'http://id.loc.gov/authorities/genreForms/gf2014026141': 'i', - 'http://id.loc.gov/authorities/genreForms/gf2014026054': 'i', - 'http://id.loc.gov/authorities/genreForms/gf2014026542': 'j', - 'http://id.loc.gov/authorities/genreForms/gf2014026488': 'p', - 'http://id.loc.gov/authorities/genreForms/gf2014026481': 'p', - 'http://id.loc.gov/authorities/genreForms/gf2011026363': 's' - } - - let useP = false - for (let gf of genreForms){ - // check URI matches - if (gf.uri && pClassTrueURIs.includes(gf.uri)){ - useP = true - break - } - if (gf.uri && pClassLetterURIs[gf.uri]){ - useP = true - break - } - // check label patterns - let label = (gf.label || '').toLowerCase() - if (label.includes('fiction') && !label.includes('onfiction')){ - useP = true - break - } - if ((gf.label || '').includes('(Fiction)')){ - useP = true - break - } - if (label.includes('humor')){ - useP = true - break - } - if (label.includes('poetry')){ - useP = true - break - } - if (label.includes('speeches')){ - useP = true - break - } - } + if (saved) { + this.activeProfileSaved = true + this.logEvent('SAVED_RECORD') + } + }, - return { - subjects: subjects, - genreForms: genreForms, - useP: useP, - useLCC: useLCC - } + /** + * Log an event to the backend, extracting user/record info from current state + * @param {string} eventType - e.g. "LOAD_FROM_LCCN", "LOAD_FROM_COPYCAT", "CREATED_RECORD" + * @param {object} opts - optional overrides: eId, lccn, instanceId, metadata + */ + logEvent: async function (eventType, opts = {}) { + let prefStore = usePreferenceStore() - }, + // get the username from the SSO JWT payload + let username = null + if (prefStore.ssoUser) { + username = prefStore.ssoUser.username || prefStore.ssoUser.name || prefStore.ssoUser.email + } + if (!username) { + username = prefStore.catInitals + } + if (!username) { + console.warn('logEvent: no username available, skipping') + return false + } - /** - * Check if, and which, up down buttons should display in the action button - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @return {array} - the first value says if the "up" button should display, the second if the "down" - */ - showUpDownButtons: function(componentGuid){ - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - let target - let targetType - - let items - let subjItems = [] - let contribItems = [] - let gfItems = [] - let idItems = [] - - if (pt !== false){ - let workRtId = null - for (let rtId in this.activeProfile.rt){ - if (rtId.indexOf(":Work") > -1){ - workRtId = rtId - for (let ptId of this.activeProfile.rt[rtId].ptOrder){ - if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject'){ - subjItems.push(ptId) - if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid){ - target = ptId - targetType = "subject" - } - } - if ( - this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/contribution' && - this.activeProfile.rt[rtId].pt[ptId].propertyLabel != "Creator of Work" - ){ - contribItems.push(ptId) - if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid){ - target = ptId - targetType = "contribution" - } - } - if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/genreForm'){ - gfItems.push(ptId) - if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid){ - target = ptId - targetType = "gf" + // pull eId and lccn from activeProfile if not provided + let eId = opts.eId || (this.activeProfile && this.activeProfile.eId) || null + let lccn = opts.lccn || null + let instanceId = opts.instanceId || null + + // try to extract lccn and instanceId from the record XML if available + if (!lccn || !instanceId) { + try { + let xml = await utilsExport.buildXML(this.activeProfile) + if (xml && xml.xlmStringBasic) { + let parser = new DOMParser() + let doc = parser.parseFromString(xml.xlmStringBasic, 'application/xml') + if (!lccn) { + let lccnEl = doc.getElementsByTagNameNS('http://id.loc.gov/ontologies/lclocal/', 'lccn')[0] + if (lccnEl) { + lccn = lccnEl.textContent + } + } + if (!instanceId) { + let extEls = doc.getElementsByTagNameNS('http://id.loc.gov/ontologies/lclocal/', 'externalid') + for (let el of extEls) { + if (el.textContent.includes('/instances/')) { + instanceId = el.textContent + break + } + } + } } - } + } catch (e) { + console.warn('logEvent: could not extract XML metadata', e) } - } - if (rtId.indexOf(":Instance") > -1){ - for (let ptId of this.activeProfile.rt[rtId].ptOrder){ - if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/identifiedBy'){ - idItems.push(ptId) - if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid){ - target = ptId - targetType = "identifier" - } - } - } - } } - } - if (targetType == "subject"){ - items = subjItems - } else if (targetType == "gf"){ - items = gfItems - } else if (targetType == "identifier"){ - items = idItems - } else { - items = contribItems - } + let eventOpts = {} + if (eId) eventOpts.eId = eId + if (lccn) eventOpts.lccn = lccn + if (instanceId) eventOpts.instanceId = instanceId + if (opts.metadata) eventOpts.metadata = opts.metadata - if (items.length <= 1){ - return [false, false] - } else { - let pos = items.indexOf(target) - if (pos == 0){ - return [false, true] - } else if (pos == items.length-1){ - return [true, false] - } else if (pos < 0){ - return [false, false] - }else { - return [true, true] - } - } - }, + return await utilsNetwork.logEvent(username, eventType, eventOpts) + }, - //This is repurposed from `makeSubjectHeadingPrimary` - /** - * Moves the selected heading up or down - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} dir - which way the item should move `up` or `down` - * @return {void} - */ - moveUpDown: function(componentGuid, dir){ - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - let target - - if (pt !== false){ - let firstHeading = null - let workRtId = null - let instRtId = null - for (let rtId in this.activeProfile.rt){ - if (rtId.indexOf(":Work") > -1){ - for (let ptId of this.activeProfile.rt[rtId].ptOrder){ - if ( - this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject' || - this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/contribution' || - this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/genreForm' - ){ - if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid){ - target = ptId - workRtId = rtId - } - } - } - } + /** + * Ask for a record and parse it to load it into the editor + * - if (rtId.indexOf(":Instance") > -1){ - for (let ptId of this.activeProfile.rt[rtId].ptOrder){ - if ( - this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/identifiedBy' - ){ - if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid){ - target = ptId - instRtId = rtId - } - } + * @return {boolean} - did it save + */ + loadRecord: async function (eid, profile) { + let xml = await utilsExport.buildXML(this.activeProfile) + // console.log("*****") + // console.log(xml) + let saved = await utilsNetwork.saveRecord(xml.xlmStringBasic, this.activeProfile.eId) + if (saved) { + this.activeProfileSaved = true } - } - } + }, - if (target){ - let targetRt = null - if (workRtId){ - targetRt = workRtId - } else if (instRtId){ - targetRt = instRtId - } - let currentPos = this.activeProfile.rt[targetRt].ptOrder.indexOf(target) - let newPos - if (dir == "up"){ - newPos = currentPos-1 - } else { - newPos = currentPos+1 - } - - //swap the target with the element in the desired position - //delete from current pos - this.activeProfile.rt[targetRt].ptOrder.splice(currentPos, 1) - //put in it's new position - this.activeProfile.rt[targetRt].ptOrder.splice(newPos, 0, target) - - this.dataChanged() - } - } - }, + /** + * Validate the reocrd + */ + validateRecord: async function (eid, profile) { + //console.log("Profile store: Validating?") + let xml = await utilsExport.buildXML(this.activeProfile) - /** - * Moves the passed heading the first of the subjects in the PT order - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @return {void} - */ - - makeSubjectHeadingPrimary: async function(componentGuid){ - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - if (pt !== false){ - - // loop through all the headings and find the place the headings start - let firstHeading = null - let workRtId = null - for (let rtId in this.activeProfile.rt){ - if (rtId.indexOf(":Work") > -1){ - workRtId = rtId - for (let ptId of this.activeProfile.rt[rtId].ptOrder){ - if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject'){ - firstHeading=ptId - break - } - } - } - if (firstHeading){break} - } + let response = await utilsNetwork.validate(xml.xlmStringBasic) - if (firstHeading){ + return response + }, - let firstHeadingPos = this.activeProfile.rt[workRtId].ptOrder.indexOf(firstHeading) - let currentHeadingPos = this.activeProfile.rt[workRtId].ptOrder.indexOf(pt.id) - // remove the current heading from its position - this.activeProfile.rt[workRtId].ptOrder.splice(currentHeadingPos, 1); - // insert where the first heading is - this.activeProfile.rt[workRtId].ptOrder.splice(firstHeadingPos, 0, pt.id); + /** + * Publish record to backend + * - this.dataChanged() - } - } - }, + * @return {obj} - response from posting action + */ + publishRecord: async function (eid, profile) { - triggerBadXMLBuildRecovery: function(lastGoodBuild, lastGoodBuildTimetamp){ - this.showRecoveryModal = true - const timeAgo = new TimeAgo('en-US') - this.activeProfile = JSON.parse(JSON.stringify(lastGoodBuild)) - this.dataChanged() - }, - - - breakRecord: async function(componentGuid, structure){ - - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - if (pt !== false){ - - let baseURI = pt.propertyURI - if (!pt.userValue[baseURI]){ - pt.userValue[baseURI] = [{}] - } - let userValue = JSON.parse(JSON.stringify(pt.userValue[baseURI][0])) - - userValue['somting'] = {'@guid':'00000','hppts:sfsdfgfdsg.com':['helllerrlooo']} - pt.userValue[baseURI][0] = JSON.parse(JSON.stringify(userValue)) - // they changed something - this.dataChanged() - - } - - }, - - /** - * Replace a placeholder in the default value when appropriate. - * Current replacements: `[$date]` with today's date - * - * @param {string} defaultString - the incoming string with a value that will be replaced. */ - replaceDefaultPlaceHolder: function(defaultString){ - if (!defaultString){ return defaultString } - let target = defaultString.match(/\[\$(.*)\]/g) - if (!target){return defaultString} - - if (target[0].includes("date")){ - let date = new Date() - let dd = String(date.getDate()).padStart(2, '0') - let mm = String(date.getMonth() + 1).padStart(2, '0') - let yyyy = date.getFullYear() - - let today = yyyy + mm + dd - - return defaultString.replace("[$date]", today) - } // else if (target[0].includes("...")) - }, - - /** - * Set the default values of the component fields - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {object} structure - passed from the UI, the structure object - * @param {object} pp - propertyPath for the structure. There's some nested components with defaults that won't build correctly otherwise - * @return {void} - */ - - insertDefaultValuesComponent: async function(componentGuid, structure, pp=false){ - // console.log(componentGuid) - // console.log("structure",structure) - - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - //Delete related items from the cache, loading from the cache - // sometimes causes errors after inserting defaults - if (Object.keys(cachePt).includes(componentGuid)){ - delete cachePt[componentGuid] - } - for (let guid of Object.keys(cacheGuid)){ - try { - cleanCacheGuid(cacheGuid, JSON.parse(JSON.stringify(pt.userValue)), guid) - } catch { - console.warn("Couldn't clear the CacheGuid") - } - } - - let isParentTop = false - - if (pt !== false){ - let baseURI = pt.propertyURI - if (!pt.userValue[baseURI]){ - pt.userValue[baseURI] = [{}] - } - let userValue = JSON.parse(JSON.stringify(pt.userValue[baseURI][0])) - - // find the default values for this template if they exist - if (structure){ - - if (structure.parentId){ - if (structure.parentId.endsWith("Work") || structure.parentId.includes("Instance") || structure.parentId.endsWith("Hub") || structure.parentId.endsWith("Item")){ - isParentTop = true - } - - let defaultsProperty = false - if (this.rtLookup[structure.parentId]){ - for (let p of this.rtLookup[structure.parentId].propertyTemplates){ - // dose it have a default value? - if (p.valueConstraint.defaults && p.valueConstraint.defaults.length>0){ - if (p.valueConstraint.valueTemplateRefs && p.valueConstraint.valueTemplateRefs.length>0){ - // they are linking to another template in this template, so if we ant to populate the imformation we would need to know what predicate to use :((((( - if (this.rtLookup[p.valueConstraint.valueTemplateRefs[0]] && this.rtLookup[p.valueConstraint.valueTemplateRefs[0]].propertyTemplates && this.rtLookup[p.valueConstraint.valueTemplateRefs[0]].propertyTemplates.length==1){ - let defaultPropertyToUse = this.rtLookup[p.valueConstraint.valueTemplateRefs[0]].propertyTemplates[0].propertyURI - // we know what to store in the value and we now know what property to use - userValue[p.propertyURI] = [] - for (let d of p.valueConstraint.defaults){ - let value = { - '@guid': short.generate(d.defaultLiteral, d.defaultURI) - } - // do we need to create a blank node for this value? - let useType = utilsRDF.suggestTypeProfile(p.propertyURI,pt) - if (useType === false){ - // did not find it in the profile, look to the network - useType = await utilsRDF.suggestTypeNetwork(p.propertyURI) - } - if (useType && useType != 'http://www.w3.org/2000/01/rdf-schema#Literal'){ - value['@type'] = useType - value[defaultPropertyToUse] = [{ - '@guid': short.generate(d.defaultLiteral, d.defaultURI) - }] - if (d.defaultLiteral && d.defaultLiteral != ''){ - value[defaultPropertyToUse][0][defaultPropertyToUse] = this.replaceDefaultPlaceHolder(d.defaultLiteral) - } - if (d.defaultURI && d.defaultURI != ''){ - value['@id'] = d.defaultURI - } - }else{ - if ((d.defaultLiteral && !d.defaultURI) || (d.defaultLiteral != '' && d.defaultURI == '') ){ - value[defaultPropertyToUse] = this.replaceDefaultPlaceHolder(d.defaultLiteral) - }else{ - value['@id'] = d.defaultURI - } - } - userValue[p.propertyURI].push(value) - } - }else{ - console.warn("Nested default template trying to insert values but there are multiple propertyTemplates so no clue which proerpty to look into for the default value: ", this.rtLookup[p.valueConstraint.valueTemplateRefs[0]]) - } - }else{ - let blankNodeType = null - // we probably need to make a blank node, so find out what rdf type blank node is needed - if (p.valueConstraint && p.valueConstraint.valueDataType && p.valueConstraint.valueDataType.dataTypeURI){ - blankNodeType = p.valueConstraint.valueDataType.dataTypeURI - } - // overwrite it if there is anything there already - userValue[p.propertyURI] = [] - for (let d of p.valueConstraint.defaults){ - let value = { - '@guid': short.generate(d.defaultLiteral, d.defaultURI) - } - // if it just has a literal value and not a URI then don't create a blank node, just insert it using that literal property - if ((d.defaultLiteral && !d.defaultURI) || (d.defaultLiteral != '' && d.defaultURI == '') ){ - value[p.propertyURI] = this.replaceDefaultPlaceHolder(d.defaultLiteral) - }else{ - // it is a blank node - if (d.defaultLiteral){ - // console.log(newPt) - value['http://www.w3.org/2000/01/rdf-schema#label'] = [{ - '@guid': short.generate(), - 'http://www.w3.org/2000/01/rdf-schema#label': this.replaceDefaultPlaceHolder(d.defaultLiteral) - }] - } - if (d.defaultURI){ - value['@id'] = d.defaultURI - } - if (blankNodeType){ - value['@type'] = blankNodeType - } - } - // if we're not working at the top level, just add the default values - if (!pp){ - if (!isParentTop){ - userValue[p.propertyURI].push(value) - //otherwise, make sure the propertyURI matches the baseURI - } else if (isParentTop && p.propertyURI == baseURI){ - userValue[p.propertyURI].push(value) - } - } else { - let existing = utilsProfile.returnValueFromPropertyPath(pt, pp) // don't overwrite existing data - if (!existing && ["http://id.loc.gov/ontologies/bibframe/relationship", "http://id.loc.gov/ontologies/bibframe/status"].includes(p.propertyURI)){ - this.setValueSimple(componentGuid, null, pp, d.defaultURI, d.defaultLiteral) - } - } - } - } + let postingHub = false + if (this.activeProfile && this.activeProfile.id && this.activeProfile.id.indexOf(':Hub') > -1) { + // ITS A HUB! + // do other things if its a hub + postingHub = true + } - } - } + let xml = await utilsExport.buildXML(this.activeProfile) - } - }else{ - console.warn("No structure.parentId found") - } - }else{ - alert("Error: no structure found") - } - - if (!isParentTop){ - const oldValues = Object.keys(pt.userValue[baseURI][0]) - const newValues = Object.keys(JSON.parse(JSON.stringify(userValue))) - - // don't overwrite existing values - let matches = newValues.filter((nV) => !oldValues.includes(nV)) - - Object.filter = (obj, predicate) => - Object.keys(obj) - .filter( key => predicate(key) ) - .reduce( (res, key) => Object.assign(res, { [key]: obj[key] }), {} ); - - let filtered = Object.filter(JSON.parse(JSON.stringify(userValue)), key => matches.includes(key)) - - Object.assign(pt.userValue[baseURI][0], JSON.parse(JSON.stringify(filtered))) - } else { - //We're not in a nested component, so we can just set the userValue - // But don't overwrite an existing value - for (let key in pt.userValue){ - if (!key.startsWith('@') && typeof pt.userValue[key][0] == 'object' && Object.keys(pt.userValue[key][0]).length == 0){ - pt.userValue = JSON.parse(JSON.stringify(userValue)) - } - } - } - // they changed something - this.dataChanged() - - }else{ - console.error('insertDefaultValuesComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) - } - }, - - - /** - * Duplicate / create new component - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {boolean} createEmpty - if true make the component have no pre-populated data, if false "duplicate" the data from the source component - * @return {void} - */ - duplicateComponent: async function(componentGuid, structure){ - let createEmpty = true - - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/adminMetadata'){ - alert("Cannot create Admin Metadata") - return false - } + let pubResuts + let marva001 - //Ensure that the component is going to the right place by checking the structure.parentID - // the parentId of different kinds of titles don't include `work` or `instance`, so check the RT in the profile - let rt = utilsProfile.getRtTypeFromGuid(this.activeProfile, componentGuid) - let actionTarget = null - if (structure.parentId.includes("Instance") || rt.includes("Instance")) { - actionTarget = "Instance" - } else if (structure.parentId.includes("Work") || rt.includes("Work")) { - actionTarget = "Work" - } - - if (pt !== false){ - let profile - let propertyPosition - for (let r of this.activeProfile.rtOrder){ - propertyPosition = this.activeProfile.rt[r].ptOrder.indexOf(pt.id) - - if (propertyPosition != -1 && (r.includes(rt) || actionTarget == null)){ - - profile = r - break - } - } + if (postingHub) { + pubResuts = await utilsNetwork.publish(xml.xlmStringBasic, this.activeProfile.eId, { id: 'Hub' }) + } else { + pubResuts = await utilsNetwork.publish(xml.xlmStringBasic, this.activeProfile.eId, this.activeProfile) + } - let key = pt.propertyURI.replace('http://','').replace('https://','').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"_") + '__' + ((pt.propertyLabel) ? pt.propertyLabel.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g,'_').toLowerCase() : "plabel") - let newPropertyId = key + '_'+ (+ new Date()) + pubResuts.resourceLinks = [] + // if it was accepted by the system send it to the marva backend to store as posted + if (pubResuts.status) { + this.activeProfile.status = 'published' + await this.saveRecord() - let newPt = JSON.parse(JSON.stringify(pt)) + this.logEvent('PUBLISHED_RECORD') - // cleanout the newPt - newPt.xmlSource = "" - newPt.dataLoaded = false - newPt.hasData = false - delete newPt.deleted - delete newPt.hideSubject - delete newPt.hideClassNum - delete newPt.hide + const config = useConfigStore() - newPt.id = newPropertyId - newPt['@guid'] = short.generate() + for (let rt in this.activeProfile.rt) { + let type = rt.split(':').slice(-1)[0] + // this.localMarva = false + let url = config.convertToRegionUrl(this.activeProfile.rt[rt].URI) + if (this.activeProfile.marvaLocalId) { + url = url.replace(this.activeProfile.eId, this.activeProfile.marvaLocalId) + } + let env = config.returnUrls.env + // populate the title + if (type == 'Instance') { + let bibId = this.activeProfile.rt[rt].URI.split("/")[this.activeProfile.rt[rt].URI.split('/').length - 1] + document.title = `Marva | ${bibId}`; + } - // console.log("Lookign at this PT", pt) - // console.log(this.activeProfile) - // console.log(propertyPosition) - // console.log(key,newPropertyId) - if (createEmpty){ + pubResuts.resourceLinks.push({ + 'type': type, + 'url': url, + 'env': env + }) + } + } + return pubResuts + }, - // store.state.activeUndoLog.push(`Added another property ${exportXML.namespaceUri(activeProfile.rt[profile].pt[id].propertyURI)}`) - // console.log(activeProfile.rt[profile].pt[newPropertyId]) - // console.log(profile,newPropertyId) - newPt.userValue = { - '@guid': short.generate(), - '@root' : newPt.propertyURI - } - if (newPt.activeType){ - newPt.userValue[newPt.propertyURI] = [ - { - '@type': newPt.activeType - } - ] - } - - - - - // we also want to add any default values in if it is just a empty new property and not duping - let idPropertyId = newPt.propertyURI - let baseURI = newPt.propertyURI - // let defaults = null - let defaultsProperty - - let useProfile = profile - // if the profile is a multiple, like lc:RT:bf2:Monograph:Item-0 split off the -0 for it to find it in the RT lookup - if (!this.rtLookup[useProfile]){ - if (useProfile.includes('-')){ - useProfile = useProfile.split('-')[0] - } - } - // first check the top level - if (this.rtLookup[useProfile]){ - defaultsProperty = this.rtLookup[useProfile].propertyTemplates.filter((x)=>{ return (x.propertyURI === idPropertyId) ? true : false}) - if (defaultsProperty.length>0){ - defaultsProperty=defaultsProperty[0] - - } - } - }else{ - // doesn't support duplicating components yet - } - this.activeProfile.rt[profile].pt[newPropertyId] = JSON.parse(JSON.stringify(newPt)) - this.activeProfile.rt[profile].ptOrder.splice(propertyPosition+1, 0, newPropertyId); + /** + * returns the label to use in bf code mode + * + * @param {object} structure - the structure value from the profile + * @param {boolean} title - return the title text not the short code + * @return {string} - the label + */ + returnBfCodeLabel: function (structure, returntitle, componentGuid) { - // reset this, otherwise it can cause unwanted behavior in the XML - this.activeProfile.rt[profile].pt[newPropertyId].deepHierarchy = false - if (structure){ - this.insertDefaultValuesComponent(newPt['@guid'], structure) - } + if (returntitle) { + let pt = utilsProfile.returnPt(this.activeProfile, structure['@guid']) + if (pt) { + return utilsExport.namespaceUri(pt.propertyURI) + } else { + return utilsExport.namespaceUri(structure.propertyURI) + } - // they changed something - this.dataChanged() + } - // send back the component guid incase the UI needs to do something with it - return newPt['@guid'] + let code = utilsParse.namespaceUri(structure.propertyURI) + // console.log(structure.propertyURI, code) - }else{ - console.error('duplicateComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) + // HACK HACK HAVK + if (structure.propertyURI == 'http://www.loc.gov/mads/rdf/v1#Topic') { + code = "bf:subject" + } - } - }, + if (['rdfs:label', 'owl:sameAs'].includes(code)) { + // console.log(structure.propertyURI) + code = utilsParse.namespaceUri(this.rtLookup[structure.parentId].resourceURI) + } + + let orgCode = code - /** - * Duplicate / create new component with a given userValue - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {object} structure - structure of the component being copied - * @param {string} profileName - name of the target profile - * @param {string} predecessor - id of componenet comes before in the order - * @return {array} the id and guid of the newPropertyId - */ - duplicateComponentGetId: async function(componentGuid, structure, profileName, predecessor){ - let createEmpty = true + code = code.replace('bflc:', 'lc:') + + // TODO - why are these classes? + // bf:Note becomes bf:note + if (code.charAt(3) === code.charAt(3).toUpperCase()) { + code = code.substring(0, 3) + code.charAt(3).toLowerCase() + code.substring(3 + 1); + } + // console.log("code=",code) - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) + // if we have it customized then just return that + if (shortCodesOverrides[code]) { + return shortCodesOverrides[code] + } - if (pt !== false){ - let profile - let propertyPosition - let key = pt.propertyURI.replace('http://','').replace('https://','').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"_") + '__' + ((pt.propertyLabel) ? pt.propertyLabel.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g,'_').toLowerCase() : "plabel") + let justProperty = code.split(':')[1] - let lastPosition = 0 + let numUpper = justProperty.length - justProperty.replace(/[A-Z]/g, '').length; - for (let r of this.activeProfile.rtOrder){ - let tempPosition = JSON.parse(JSON.stringify(this.activeProfile.rt[r].ptOrder)).indexOf(predecessor) - if (tempPosition > 0){ - propertyPosition = tempPosition + if (numUpper == 2) { + code = code.split(':')[0] + ':' + justProperty.charAt(0) + justProperty.replace(/[a-z]/g, '') + } else if (numUpper == 1) { + code = code.split(':')[0] + ':' + justProperty.charAt(0) + justProperty.charAt(1) + justProperty.replace(/[a-z]/g, '') + } else if (numUpper == 0) { + code = code.split(':')[0] + ':' + justProperty.charAt(0) + justProperty.charAt(1) + justProperty.charAt(2) } + // console.log("code=",code) - //find the last position in the order of related components so we can insert - // the new components at the end of that list - if (r == profileName){ // does this have any sideffects?! - for (let idx in this.activeProfile.rt[r].ptOrder){ - let item = this.activeProfile.rt[r].ptOrder[idx] - //TODO: fix order when there's a deleted element? Can't reproduce now - if (item.includes(key)){ - lastPosition = idx + // if its just a rdf value then replace it with the acutal text + if (code == 'rdf:val') { + let useLabel = structure.propertyLabel.split(' ')[0] + if (useLabel.length <= 6) { + code = useLabel.toLowerCase() + } else { + code = `${useLabel.charAt(0)}${useLabel.charAt(1)}${useLabel.charAt(2)}${useLabel.charAt(3)}${useLabel.charAt(4)}${useLabel.charAt(5)}`.toLowerCase() } + // console.log("HEY CODE IS",code) } - profile = profileName - } - } - let newPropertyId = key + '_'+ (+ new Date()) + // if (uri == '') - let newPt = JSON.parse(JSON.stringify(pt)) - newPt.id = newPropertyId - newPt['@guid'] = short.generate() + // chop off the namespace + if (code.includes(':')) { + code = code.split(':')[1] + } + // console.log('****!',orgCode, ',', (orgCode.includes('bf:')) ? `https://id.loc.gov/ontologies/bibframe.html#p_${orgCode.split(':')[1]}` : 'bflc or other', ',',code) - // console.log("Lookign at this PT", pt) - // console.log(this.activeProfile) - // console.log(propertyPosition) - // console.log(key,newPropertyId) - if (createEmpty){ - newPt.userValue = { - '@guid': short.generate(), - '@root' : newPt.propertyURI - } + // console.log(code) + // console.log(structure.propertyURI) + // console.log(this.rtLookup[structure.parentId]) - if (newPt.activeType){ - newPt.userValue[newPt.propertyURI] = [ - { - '@type': newPt.activeType - } - ] - } - - // we also want to add any default values in if it is just a empty new property and not duping - let idPropertyId = newPt.propertyURI - let baseURI = newPt.propertyURI - // let defaults = null - let defaultsProperty - - // If it's deleted, flip it - if (newPt.deleted){ - newPt.deleted = false - } - - let useProfile = profile - // if the profile is a multiple, like lc:RT:bf2:Monograph:Item-0 split off the -0 for it to find it in the RT lookup - if (!this.rtLookup[useProfile]){ - if (useProfile.includes('-')){ - useProfile = useProfile.split('-')[0] - } - } - // first check the top level - if (this.rtLookup[useProfile]){ - defaultsProperty = this.rtLookup[useProfile].propertyTemplates.filter((x)=>{ return (x.propertyURI === idPropertyId) ? true : false}) - if (defaultsProperty.length>0){ - defaultsProperty=defaultsProperty[0] - - } - } - }else{ - // doesn't support duplicating components yet - } - - this.activeProfile.rt[profile].pt[newPropertyId] = JSON.parse(JSON.stringify(newPt)) - // For moving titles between work/instance, we want to use the last postion, otherwise - // should be after the predecessor - if (predecessor == 'last'){ - this.activeProfile.rt[profile].ptOrder.splice(Number(lastPosition)+1, 0, newPropertyId) - } else { - this.activeProfile.rt[profile].ptOrder.splice(Number(propertyPosition)+1, 0, newPropertyId) - } + return code + }, - if (structure){ - this.insertDefaultValuesComponent(newPt['@guid'], structure) - } - - // they changed something - this.dataChanged() - - return [newPropertyId, newPt['@guid']] + /** + * returns if the request property is the "main" property of that component + * + * @param {string} componentGuid - the guid of the component + * @param {array} propertyPath - the property path of the property in question + * @return {boolean} - + */ + inlineIsMainProperty: function (componentGuid, fieldStructure, propertyPath) { - }else{ - console.error('duplicateComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) - return false - } - }, + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + // let valueLocation = utilsProfile.returnValueFromPropertyPath(pt,propertyPath) + // some hard coded hacks - /** - * Delete existing component - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @return {void} - */ - deleteComponent: async function(componentGuid){ - // locate the correct pt to work on in the activeProfile - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - - if (pt !== false){ - if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/adminMetadata'){ - alert("Cannot delete Admin Metadata") - return false - } + if (fieldStructure.propertyURI === 'http://id.loc.gov/ontologies/bflc/nonSortNum') { + return false + } + if (fieldStructure.propertyURI === 'http://id.loc.gov/ontologies/bibframe/mainTitle') { + return true + } - // the checklabel will be the URI and the label of the component, beceause there are some components that use the same property URI - let checkLabel = pt.propertyLabel + pt.propertyURI - - //adjust for items - if (pt.parentId.includes(":Item")){ - for (let rt in this.activeProfile.rt){ - if (rt.includes(pt.parentId)){ - pt.parentId = rt - break - } - } - } - // first see how many these properties exist in the resource - let propertyCount = 0 - for (let k in this.activeProfile.rt[pt.parentId].pt){ - if (this.activeProfile.rt[pt.parentId].pt[k].propertyLabel + this.activeProfile.rt[pt.parentId].pt[k].propertyURI == checkLabel && !this.activeProfile.rt[pt.parentId].pt[k].deleted){ - propertyCount++ - } - } - // if the propertyCount is 1 then we are about to delete the only property - // so instead just blank out the user value so it still exists if they need to add a value - if (propertyCount>1){ - this.activeProfile.rt[pt.parentId].pt[pt.id].deleted = true - }else{ - for (let key in this.activeProfile.rt[pt.parentId].pt[pt.id].userValue){ - if (!key.startsWith('@')){ - delete this.activeProfile.rt[pt.parentId].pt[pt.id].userValue[key] + // if it doesn't have any valuetemplates then it is a single property component + if (pt.valueConstraint.valueTemplateRefs.length == 0) { + return true + } - // Cleanup for components that are uneditable - if (Object.keys(this.activeProfile.rt[pt.parentId].pt[pt.id]).includes("xmlSource")){ - delete this.activeProfile.rt[pt.parentId].pt[pt.id].xmlSource - this.activeProfile.rt[pt.parentId].pt[pt.id].hasData = false - this.activeProfile.rt[pt.parentId].pt[pt.id].deepHierarchy = false - this.activeProfile.rt[pt.parentId].pt[pt.id].dataLoaded = false - this.activeProfile.rt[pt.parentId].pt[pt.id].xmlHash = "" + // if the label matches the first label in one of the value templates then it is the first property and probably the most important? + for (let ref of pt.valueConstraint.valueTemplateRefs) { + if (this.rtLookup[ref].propertyTemplates[0].propertyLabel === fieldStructure.propertyLabel) { + return true } - } } - } - - this.dataChanged() - - - }else{ - console.error('deleteComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) - console.log(JSON.stringify(this.activeProfile)) - } - - - }, - - /** - * When they change something run this function to update things like autosave ect - * - * @return {void} - */ - dataChanged: function(){ - this.activeProfileSaved = false - - // this is a debounce so it doesn't run on every key stroke - window.clearTimeout(dataChangedTimeout) - dataChangedTimeout = window.setTimeout(()=>{ - this.setMostCommonNonLatinScript() - // also store it in the active profile - this.activeProfile.mostCommonNonLatinScript = this.mostCommonNonLatinScript - this.activeProfile.nonLatinScriptAgents = this.nonLatinScriptAgents - // console.log("this.activeProfilethis.activeProfilethis.activeProfile",this.activeProfile) - // this will trigger the preview rebuild - this.dataChangedTimestamp = Date.now() - // console.log("Data changed, this.activeProfile", this.activeProfile) - - // save the current record for undo - this.saveState() - - // if they have auto save on then save it also - if (usePreferenceStore().returnValue('--b-general-auto-save')){ - this.saveRecord() - } - - },500) - }, - - /** - * A helper that can be run before loading a new record to do any maintenance needed - * - * @return {void} - */ - prepareForNewRecord: function(){ - - this.activeProfile = {} - this.linkedData = {} - - }, - - + return false - /** - * Build a new instance - * - * @param secondary {bool} whether this should an instance or a secondary instance - * @param lccn {string} the lccn for the record - * @return {void} - */ - createInstance: function(secondary=false, lccn=null){ - // find the RT for the instance of this profile orginally - // get the work rt + }, + /** + * returns if the request property has a value in it + * + * @param {string} componentGuid - the guid of the component + * @param {array} propertyPath - the property path of the property in question + * @return {boolean} - + */ + inlinePropertyHasValue: function (componentGuid, fieldStructure, propertyPath) { - let instanceName - let instanceRt - let workUri + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + let valueLocation = utilsProfile.returnValueFromPropertyPath(pt, propertyPath) + if (valueLocation !== false) { + return true + } + return false - for (let rtId in this.activeProfile.rt){ - if (rtId.includes(":Work")){ - workUri = this.activeProfile.rt[rtId].URI - // now find the corosponding instance id - for (let allRt in this.profiles){ - if (this.profiles[allRt].rtOrder.indexOf(rtId)>-1){ - instanceName = this.profiles[allRt].rtOrder.filter(i => i.includes(":Instance"))[0] - instanceRt = JSON.parse(JSON.stringify(this.profiles[allRt].rt[instanceName])) - } - } - } - } - let instanceCount = 0; - - // gather info to add it - let instances = Object.keys(this.activeProfile.rt).filter(i => i.includes(":Instance")) - if (instances.length>1){ - instanceCount = instances.length -1 - } - // console.log('instances',instances) - // for (let i of instances){ - // if (i.includes('_')){ - // let nid = parseInt(i.split('_')[1]) - // if (nid > instances.length){ - // instanceCount = nid - // } - // } - // } - - instanceCount++ - // console.log('instanceCount',instanceCount) - let newRtId = instanceName +'_'+instanceCount - instanceRt.isNew = true - this.activeProfile.rt[newRtId] = instanceRt - this.activeProfile.rtOrder.push(newRtId) - - // give it all new guids - for (let pt in this.activeProfile.rt[newRtId].pt){ - this.activeProfile.rt[newRtId].pt[pt]['@guid'] = short.generate() - // update the parentId - this.activeProfile.rt[newRtId].pt[pt].parentId = this.activeProfile.rt[newRtId].pt[pt].parentId.replace(instanceName, newRtId) - this.activeProfile.rt[newRtId].pt[pt].parent = this.activeProfile.rt[newRtId].pt[pt].parent.replace(instanceName, newRtId) - - // If it's not mandatory, add it to ad hoc mode's emptyComponents list - this.addToAdHocMode(newRtId, pt) - } - - - - // setup the new instance's properies - // profile.rt[newRdId].URI = 'http://id.loc.gov/resources/instances/'+ translator.toUUID(translator.new()) - - if (lccn != ""){ - this.activeProfile.rt[newRtId].URI = "http://id.loc.gov/resources/instances/" + lccn - } else { - this.activeProfile.rt[newRtId].URI = utilsProfile.suggestURI(this.activeProfile,'bf:Instance', workUri) - } - this.activeProfile.rt[newRtId].instanceOf = workUri - - if (secondary && secondary != "item"){ - this.activeProfile.rt[newRtId]['@type'] = 'http://id.loc.gov/ontologies/bflc/SecondaryInstance' - } - - this.activeProfile.rt[newRtId].deletable = true - - //Add to rtLookup, with a copy of an instance as the value - this.rtLookup[newRtId] = this.rtLookup[instanceName] - - this.dataChanged() + }, + /** + * returns possible fields in that can be displayed in the component + * + * @return {array} - array of the fields + */ + returnPossibleFieldsInComponent: function (componentGuid) { - }, + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - /** - * Create a new item for the record - * @param instance {string} position of the rt for the instance that the item belongs to, when there is more than 1 instance - * @param lccn {string} the lccn for the record - * - * @return {void} - */ - createItem: async function(instance, lccn=null){ - // find the RT for the instance of this profile orginally - // get the work rt - - let itemName - let itemRt - let workUri - let instanceUri - - for (let rtId in this.activeProfile.rt){ - if (rtId.includes(":Work")){ - workUri = this.activeProfile.rt[rtId].URI - // now find the corresponding item id - for (let allRt in this.profiles){ - if (this.profiles[allRt].rtOrder.indexOf(rtId)>-1){ - if (this.profiles[allRt].rtOrder.filter(i => i.includes(":Item"))[0]){ - itemName = this.profiles[allRt].rtOrder.filter(i => i.includes(":Item"))[0] - itemRt = JSON.parse(JSON.stringify(this.profiles[allRt].rt[itemName])) - } - } - } - } - if (instance && rtId == instance){ - instanceUri = this.activeProfile.rt[rtId].URI - break - }else if (rtId.includes(":Instance")){ - instanceUri = this.activeProfile.rt[rtId].URI - } - } - let itemCount = 0; - // gather info to add it - let items = Object.keys(this.activeProfile.rt).filter(i => i.includes(":Item")) - if (items.length >= 1){ - itemCount = items.length - } - - - - // console.log('itemCount',itemCount) - let newRtId = itemName +'_'+itemCount - itemRt.isNew = true - this.activeProfile.rt[newRtId] = itemRt - this.activeProfile.rtOrder.push(newRtId) - - // give it all new guids - for (let pt in this.activeProfile.rt[newRtId].pt){ - this.activeProfile.rt[newRtId].pt[pt]['@guid'] = short.generate() - // update the parentId - this.activeProfile.rt[newRtId].pt[pt].parentId = this.activeProfile.rt[newRtId].pt[pt].parentId.replace(itemName, newRtId) - this.activeProfile.rt[newRtId].pt[pt].parent = this.activeProfile.rt[newRtId].pt[pt].parent.replace(itemName, newRtId) - - // If it's not mandatory, add it to ad hoc mode's emptyComponents list - this.addToAdHocMode(newRtId, pt) - } - - // setup the new instance's properies - // profile.rt[newRdId].URI = 'http://id.loc.gov/resources/instances/'+ translator.toUUID(translator.new()) - - //this.activeProfile.rt[newRtId].URI = utilsProfile.suggestURI(this.activeProfile,'bf:Item', instanceUri) - if (lccn && lccn != ""){ - this.activeProfile.rt[newRtId].URI = "http://id.loc.gov/resources/items/" + lccn - } else { - this.activeProfile.rt[newRtId].URI = utilsProfile.suggestURI(this.activeProfile,'bf:Item', instanceUri) - } - - this.activeProfile.rt[newRtId].itemOf = instanceUri - - //Add to rtLookup, with a copy of an instance as the value - this.rtLookup[newRtId] = this.rtLookup[itemName] - - this.dataChanged() + if (pt.valueConstraint.valueTemplateRefs.length == 0) { - }, + return [] + } + let use = [] - /** - * Scans the entire active profile for paired literals where some have language tags and others don't. - * - * This method looks for instances where a property contains multiple literal values, - * and at least one value has a language tag (@language) while at least one other value - * does not. - * - * used for non-latin literals tool - * - * The method examines: - * 1. All nested literals inside blank nodes - * 2. Top-level literals in properties specified in the config's groupTopLeveLiterals - * - * @returns {Array} An array of objects containing: - * - ptObj: The property template object containing the literal - * - node: The node containing the untagged literal - * - propertyURI: The URI of the property - * - value: The literal value without a language tag - */ - returnPairedLiteralsWithNoLang(){ - - let results = [] - function process (obj, func) { - if (obj && obj.userValue){ - obj = obj.userValue - } - if (Array.isArray(obj)){ - obj.forEach(function (child) { - process(child, func); - }); - }else if (typeof obj == 'object' && obj !== null){ - for (let k in obj){ - if (Array.isArray(obj[k])){ - if (!k.startsWith('@') && obj[k].length>1){ - func(obj,k,obj[k]); - } - process(obj[k], func); + if (pt.valueConstraint.valueTemplateRefs.length === 1) { + use = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates + } else { - } - } - } + if (pt.activeType) { - } + for (let ref of pt.valueConstraint.valueTemplateRefs) { + if (this.rtLookup[ref].resourceURI === pt.activeType) { - for (let rt of this.activeProfile.rtOrder){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - let ptObj = this.activeProfile.rt[rt].pt[pt] - process(ptObj, function (obj,key,value) { - // e.g. - // only array > 1 make it here - if (value.filter((v)=>{ return (v['@language'])}).length >= 1 && value.filter((v)=>{ return (v['@language'])}).length != value.length){ - // only arrays with @language in them make it here and only if they do nt all have it - - for (let n of value){ - if (!n['@language']){ - // some properties we don't care about - if (useConfigStore().excludeFromNonLatinLiteralCheck.indexOf(ptObj.propertyURI) !== -1){ - continue - } - // sepcial for paired check - if (['http://id.loc.gov/ontologies/bibframe/expressionOf'].indexOf(ptObj.propertyURI) !== -1){ - continue - } - results.push({ - ptObj:ptObj, - node: n, - propertyURI: key, - value: n[key] - }) + use = this.rtLookup[ref].propertyTemplates + break + } } - } + } else { + // just use the first template + use = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates } - }); - } - } - return results - }, - + } - /** - * - * - * @return {array} - */ - returnAllNonLatinLiterals: function(onlyAccessPoints=false){ - function process (obj, func) { + // let codes = this.returnBfCodeLabel(pt) + let useReturn = [] + for (let p of use) { + useReturn.push({ 'code': this.returnBfCodeLabel(p), 'label': p.propertyLabel, 'uri': p.propertyURI }) - if (obj && obj.userValue){ - obj = obj.userValue - } + } + return useReturn - if (Array.isArray(obj)){ - obj.forEach(function (child) { - process(child, func); - }); - }else if (typeof obj == 'object' && obj !== null){ - for (let k in obj){ - if (Array.isArray(obj[k])){ - process(obj[k], func); - }else{ - if (!k.startsWith('@')){ - func(obj,k,obj[k]); - } - } - } - } + }, - } + /** + * + * + * @return {array} - array of the fields + */ + setInlineDisplay: function (componentGuid, label) { + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + if (!pt.inlineModeDisplay) { + pt.inlineModeDisplay = {} + } + pt.inlineModeDisplay[label] = true - let nonLatinNodes = [] - for (let rt of this.activeProfile.rtOrder){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - let ptObj = this.activeProfile.rt[rt].pt[pt] + }, - // we don't care about literals inside specific types of properties - // like agent or headings but we also use this function to grab the ones specificly for agents and headings - if (onlyAccessPoints){ + /** + * + * + * @return {array} - array of the fields + */ + inlineFieldIsToggledForDisplay: function (componentGuid, label) { - if (useConfigStore().excludeFromNonLatinLiteralCheck.indexOf(ptObj.propertyURI) == -1){ - continue + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + if (!pt.inlineModeDisplay) { + return false } - }else{ - - if (ptObj && useConfigStore().excludeFromNonLatinLiteralCheck.indexOf(ptObj.propertyURI) >-1){ - continue + if (pt.inlineModeDisplay[label]) { + return true } - } - - - process(ptObj, function (obj,key,value) { - // e.g. - - - - if (!latinRegex.test(value)){ - nonLatinNodes.push({ - ptObj:ptObj, - node: obj, - propertyURI: key, - value: value, - lang: obj['@language'], - }) - } - }); - - - } - } + }, - // console.log("nonLatinNodes",nonLatinNodes) - return nonLatinNodes - }, + /** + * + * + * @return {boolean} - true or false if the pt passed will have a ref component drop down selection + */ + ptHasRefComponent: function (pt) { - returnAllNonLatinAgentOptions: function(){ - - let nonLatin = this.returnAllNonLatinLiterals(true) - let nonLatinMap = {} - // console.log("nonLatin",nonLatin) - for (let nl of nonLatin){ - let ptFound = null - for (let rt of this.activeProfile.rtOrder){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - if ( JSON.stringify(this.activeProfile.rt[rt].pt[pt].userValue).indexOf(nl.node['@guid'])>-1){ - ptFound=this.activeProfile.rt[rt].pt[pt] - } - if (ptFound){break} - } - if (ptFound){break} - } + if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length > 1) { + return true + } else { - // grab the guid as the key - if (ptFound){ - if (!nonLatinMap[ptFound['@guid']]){ - nonLatinMap[ptFound['@guid']] = { - scripts: [], - '@guid' : ptFound['@guid'], - 'propertyURI': ptFound.propertyURI, - nonLatin: this.returnLatinLabelForPt(ptFound) - } - } - if (nl && nl.node && nl.node['@language']){ - let script = nl.node['@language'].split("-")[1] - if (script){ - nonLatinMap[ptFound['@guid']].scripts.push(script) + // if it only has 1 reference template, that one might have multuple + if (pt.valueConstraint && pt.valueConstraint.valueTemplateRefs && pt.valueConstraint.valueTemplateRefs.length == 1) { + // loop through all the pts and see if any of them have multi valuetemplates + // for (let subPt of this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates){ + // if (subPt.valueConstraint && subPt.valueConstraint.valueTemplateRefs && subPt.valueConstraint.valueTemplateRefs.length > 1){ + // return true + // } + // } + + // acutally just look at the first one, we don't really care if a sub pt has multi + let subPt = this.rtLookup[pt.valueConstraint.valueTemplateRefs[0]].propertyTemplates[0] + if (subPt.valueConstraint && subPt.valueConstraint.valueTemplateRefs && subPt.valueConstraint.valueTemplateRefs.length > 1) { + return true + } + } } - } + return false + }, - } - // unique array - nonLatinMap[ptFound['@guid']].scripts = [...new Set(nonLatinMap[ptFound['@guid']].scripts)]; + /** + * Flips the canBeHidden flag on a property and reordereds it to the the end of the property list + * In adhoc mode this makes it look like its being added to the profile + * @return {void} - + */ + setPropertyVisible: function (profile, component) { - } - // console.log("nonLatinMap",nonLatinMap) + this.activeProfile.rt[profile].pt[component].canBeHidden = false - return nonLatinMap - }, + this.activeProfile.rt[profile].ptOrder.push(this.activeProfile.rt[profile].ptOrder.splice(this.activeProfile.rt[profile].ptOrder.indexOf(component), 1)[0]); - returnLatinLabelForPt: function(pt){ - let nonLatinFound = null + }, - function process (obj, func) { - if (nonLatinFound){return} - if (obj && obj.userValue){ - obj = obj.userValue - } - if (Array.isArray(obj)){ - obj.forEach(function (child) { - process(child, func); - }); - }else if (typeof obj == 'object' && obj !== null){ - for (let k in obj){ - if (Array.isArray(obj[k])){ - process(obj[k], func); - }else{ - if (!k.startsWith('@')){ - func(obj,k,obj[k]); - } - } - } - } - } + /** + * Loads the record from marva backend by E id + * + * @return {void} - + */ + loadRecordFromBackend: async function (eid) { + // loading a fresh record into the editor, clear the "posted" UI flag so the + // post button doesn't stay green from a previously posted record + this.activeProfilePosted = false + this.activeProfilePostedTimestamp = false + this.activeProfile = await utilsProfile.loadRecordFromBackend(eid) + }, - process(pt, function (obj,key,value) { - if (key == 'http://www.w3.org/2000/01/rdf-schema#label'){ - if (!obj['@language']){ - nonLatinFound = obj['http://www.w3.org/2000/01/rdf-schema#label'] - } - } - }); + /** + * Pass the component GUID to use and it will insert a MLC number into the userValue + * based on the dimensions in the instance otherwise it will ask the user to give the size + **/ + insertMLCNumber: async function (componentGuid) { + + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + let enhanceResults = await this.mlcNumberEnhance() + + // console.log("enhance results",enhanceResults) + // look for the dimensions in the instance + let dimensions = null + for (let rtId in this.activeProfile.rt) { + if (this.activeProfile.rt[rtId].URI && this.activeProfile.rt[rtId].URI.indexOf('/instances/') > -1) { + let instancePt = this.activeProfile.rt[rtId].pt + for (let ptId in instancePt) { + if (instancePt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/dimensions' && instancePt[ptId].userValue && instancePt[ptId].userValue['http://id.loc.gov/ontologies/bibframe/dimensions'] && instancePt[ptId].userValue['http://id.loc.gov/ontologies/bibframe/dimensions'][0]['http://id.loc.gov/ontologies/bibframe/dimensions']) { + dimensions = instancePt[ptId].userValue['http://id.loc.gov/ontologies/bibframe/dimensions'][0]['http://id.loc.gov/ontologies/bibframe/dimensions'] + break + } + } + if (dimensions) { + break + } + } + } + if (dimensions) { + let size = parseDimensions(dimensions) + // if it wasnt able to parse it unset it + if (!size || !size.size) { + dimensions = null + } else { + dimensions = size.size + } + } - return nonLatinFound - }, + if (!dimensions) { + dimensions = prompt("Could not find dimensions in the record. Please enter the MLC size to use: S, M, L or F") + // check they did it right + if (dimensions && ['S', 'M', 'L', 'F'].includes(dimensions.toUpperCase())) { + dimensions = dimensions.toUpperCase() + } else { + alert("Invalid size entered. Try inserting MLC again and enter S, M, L or F. ") + return + } - /** - * Set lang of literal value - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} fieldGuid - the guid of the field - * @param {string} lang - the ISO rdf language value like 'en' to append to the literal 'xxxxx@en' - * @return {void} - */ - setBulkLang: function(componentGuid, fieldGuid, lang){ + } else { + // it did parse scuessfully convert the lib respomse into the size letter + if (dimensions == 'small') { + dimensions = 'S' + } else if (dimensions == 'medium') { + dimensions = 'M' + } else if (dimensions == 'large') { + dimensions = 'L' + } else if (dimensions == 'oversize' || dimensions == 'folio') { + dimensions = 'F' + } else { + alert("Error in parsing dimensions. ", dimensions) + return + } + } + // now ask the API for the next number + let number = await utilsNetwork.getMLCNumber(dimensions.toLowerCase()) + this.logEvent('REQUESTED_MLC_NUMBER', { metadata: [number] }) - let pt = utilsProfile.returnPt(this.activeProfile,componentGuid) - if (pt !== false){ - let blankNode = utilsProfile.returnGuidLocation(pt.userValue,fieldGuid) - if (blankNode){ - if (lang){ - blankNode['@language'] = lang - }else{ - if (blankNode['@language']){ - delete blankNode['@language'] + let division = usePreferenceStore().returnValue('--b-shelflist-mlc-division') + if (division && division != "") { + // they have used a divsion before or set it so insert the value in the correct place in the MLC number + number = number.slice(0, 4) + division + number.slice(4) } - } + if (enhanceResults.useP) { + number = number + ' (P)' + } else if (enhanceResults.useLCC) { + number = number + ` (${enhanceResults.useLCC})` + } - }else{ - console.warn("Cannot find blankNode",pt) - } - }else{ - console.warn("Cannot find pt",componentGuid) - } + // now update the userValue of the pt with that number + // it goes into the userValue -> http://id.loc.gov/ontologies/bibframe/classification[0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] + if (!pt.userValue) { + pt.userValue = {} + } + let dataFieldGuid = short.generate() + if (!pt.userValue['http://id.loc.gov/ontologies/bibframe/classification']) { + pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'] = [{ + "@guid": short.generate(), + "@type": "http://id.loc.gov/ontologies/bibframe/ClassificationLcc", + "http://id.loc.gov/ontologies/bibframe/assigner": [{ + "@guid": short.generate(), + "@id": "http://id.loc.gov/vocabulary/organizations/dlc", + "@type": "http://id.loc.gov/ontologies/bibframe/Agent", + "http://www.w3.org/2000/01/rdf-schema#label": [{ + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "United States, Library of Congress" + }] + }], + "http://id.loc.gov/ontologies/bibframe/classificationPortion": [{ + "@guid": dataFieldGuid, + "http://id.loc.gov/ontologies/bibframe/classificationPortion": number + }], + "http://id.loc.gov/ontologies/bibframe/status": [{ + "@guid": short.generate(), + "@id": "http://id.loc.gov/vocabulary/mstatus/uba", + "@type": "http://id.loc.gov/ontologies/bibframe/Status", + "http://www.w3.org/2000/01/rdf-schema#label": [{ + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "used by assigner" + }] + }] + }] + } else { + let classEntry = pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] + if (!classEntry['@guid']) classEntry['@guid'] = short.generate() + if (!classEntry['@type']) classEntry['@type'] = "http://id.loc.gov/ontologies/bibframe/ClassificationLcc" + if (!classEntry['http://id.loc.gov/ontologies/bibframe/assigner']) { + classEntry['http://id.loc.gov/ontologies/bibframe/assigner'] = [{ + "@guid": short.generate(), + "@id": "http://id.loc.gov/vocabulary/organizations/dlc", + "@type": "http://id.loc.gov/ontologies/bibframe/Agent", + "http://www.w3.org/2000/01/rdf-schema#label": [{ + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "United States, Library of Congress" + }] + }] + } + if (!classEntry['http://id.loc.gov/ontologies/bibframe/status']) { + classEntry['http://id.loc.gov/ontologies/bibframe/status'] = [{ + "@guid": short.generate(), + "@id": "http://id.loc.gov/vocabulary/mstatus/uba", + "@type": "http://id.loc.gov/ontologies/bibframe/Status", + "http://www.w3.org/2000/01/rdf-schema#label": [{ + "@guid": short.generate(), + "http://www.w3.org/2000/01/rdf-schema#label": "used by assigner" + }] + }] + } + if (!classEntry['http://id.loc.gov/ontologies/bibframe/classificationPortion']) { + classEntry['http://id.loc.gov/ontologies/bibframe/classificationPortion'] = [{ + "@guid": dataFieldGuid, + "http://id.loc.gov/ontologies/bibframe/classificationPortion": number + }] + } else { + classEntry['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0] = { + "@guid": dataFieldGuid, + "http://id.loc.gov/ontologies/bibframe/classificationPortion": number + } + } + } - }, + // remove any empty classification entries (cloned but never populated) + for (let rtId in this.activeProfile.rt) { + for (let ptId in this.activeProfile.rt[rtId].pt) { + let ptObj = this.activeProfile.rt[rtId].pt[ptId] + if (ptObj.propertyURI === 'http://id.loc.gov/ontologies/bibframe/classification' && ptObj.userValue) { + if (!ptObj.userValue['http://id.loc.gov/ontologies/bibframe/classification']) { + // userValue only has @root, nothing else — mark as deleted + ptObj.deleted = true + } else { + let classArr = ptObj.userValue['http://id.loc.gov/ontologies/bibframe/classification'] + for (let i = classArr.length - 1; i >= 0; i--) { + if (!classArr[i]['http://id.loc.gov/ontologies/bibframe/classificationPortion']) { + classArr.splice(i, 1) + } + } + } + } + } + } - /** - * returns the validation status of the heading used - * - * @param {string} componentGuid - the guid of the component (the parent of all fields) - * @param {string} fieldGuid - the guid of the field - */ - returnValidationType: function(fieldGuid){ - let fieldValue=null - for (let rt of this.activeProfile.rtOrder){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - fieldValue = this.activeProfile.rt[rt].pt[pt] ? utilsProfile.returnGuidLocation(this.activeProfile.rt[rt].pt[pt].userValue,fieldGuid) : false - if (fieldValue){break} - } - if (fieldValue){break} - } - if (fieldValue){ - // if it has a component list then check all the components - if (fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'] && fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'].length>0){ - - let allHasURI = true - let firstHasURI = false - for (let c of fieldValue['http://www.loc.gov/mads/rdf/v1#componentList']){ - if (!c['@id']){ allHasURI=false} - } - if (fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'][0] && fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id']){ - firstHasURI=true - } - - if (allHasURI){return ['done_all','Linked']} - if (firstHasURI){return ['warning','Partially Linked']} - - return ['help','No Partial Link'] - } + this.dataChanged() + return dataFieldGuid + }, - } + /** + * If it is a LCC component info about the LCC numbers + * used in the interface rendering + * @return {boolean} + */ + returnLccInfo: function (componentGuid) { + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - return ['report','No Link'] - }, + // if it is empty and brand new dont do the next check it wont have any data + if (Object.keys(pt.userValue).length > 1) { // this means it doesn't only have @root in the userValue and has data populated - copySelected: async function(deleteSelected=false){ - let components = [] - let compontGuids = [] - let copyTargets = document.querySelectorAll('input[class=copy-selection]:checked') + // maybe it it is a dewy or other classifciation, only proceed if it is LCC + if (pt.userValue?.['http://id.loc.gov/ontologies/bibframe/classification']?.[0]?.['@type'] !== 'http://id.loc.gov/ontologies/bibframe/ClassificationLcc') { + // console.log("RETURN FALSE 1") - if (copyTargets.length == 0){ - console.warn("nothing to copy") - alert("Nothing selected to copy. Select the fields you would like to copy.") + return false - return false - } + } - copyTargets.forEach((item) => compontGuids.push(item.id)) + } - for (const guid of compontGuids){ - let component = utilsProfile.returnPt(this.activeProfile, guid) - let componentString = JSON.stringify(component) - components.push(componentString) - if (deleteSelected){ - this.deleteComponent(guid) - } - } - //copy it - let value = components.join(" ;;; ") - const type = "text/plain" - const blob = new Blob([value], {type}) - const data = [new ClipboardItem({[type]: blob})] + let classNumber = null + let classGuid = null - await navigator.clipboard.write(data) + let cutterNumber = null + let cutterGuid = null - //Add checkmark - let button = document.getElementById("copy-selected-button") - button.children[0].innerHTML = "check" + let work = null + let inst = null + let title = null + let titleNonSort = null + let firstSubject = null + let secondSubject = null + let contributors = [] + let subtitle = null - //wait a few seconds and remove the check mark - setTimeout(function(){ - button.children[0].innerHTML = "content_copy" - }, 2000) + let titleNonLatin = null - return true - }, + // find the work and pull out stuff + for (let rtId in this.activeProfile.rt) { + let target = false - //loop through the copied data and change all the "@guid"s - changeGuid: function(data){ - try{ - for (let key of Object.keys(data)){ - if (key == "@guid"){ - data[key] = short.generate() - } else if(Array.isArray(data[key])) { - this.changeGuid(data[key]) - } else if (typeof data[key] == "object"){ - this.changeGuid(data[key]) + if (rtId.indexOf(":Work") > -1) { + for (let ptId in this.activeProfile.rt[rtId].pt) { + if (this.activeProfile.rt[rtId].pt[ptId]['@guid'] == componentGuid) { + work = this.activeProfile.rt[rtId] + break + } + } + if (work) { break } } } - } catch(e) { - console.error("Error in changeGuid: ", e) - } - }, - - //parse the activeProfile and insert the copied data where appropriate - /** - * - * @param {Object} newComponent - The new component - * @param {String} sourceRt - RT it came from - * @param {String} incomingTargetRt - RT it's going to - */ - parseActiveInsert: async function(newComponent, sourceRt=null, incomingTargetRt=null){ - this.changeGuid(newComponent) - let profile = this.activeProfile - - // handle pasting into a profileRT that doesn't exist in the new profile - // This is for when the source is an additional Instance, that doesn't exist in - // in the title - let targetRt - if (!profile.rtOrder.includes(newComponent.parentId)){ - if (newComponent.parentId.includes("_")){ - targetRt = newComponent.parentId.split("_").at(0) - } else { - targetRt = newComponent.parentId - } - } else { - targetRt = newComponent.parentId - } - - if (incomingTargetRt){ - targetRt = incomingTargetRt - } - - for (let rt in profile["rt"]){ - let frozenPts = profile["rt"][rt]["pt"] - let order = profile["rt"][rt]["ptOrder"] - - for (let pt in frozenPts){ - let current = profile["rt"][rt]["pt"][pt] - if (rt == targetRt){ - let targetURI = newComponent.propertyURI - let targetLabel = newComponent.propertyLabel - - if (!current.deleted && current.propertyURI.trim() == targetURI.trim() && current.propertyLabel.trim() == targetLabel.trim()){ - let currentPos = order.indexOf(current.id) - let newPos = order.indexOf(newComponent.id) - - // if (Object.keys(current.userValue).length == 1){ - if (this.isEmptyComponent(current)){ - current.userValue = newComponent.userValue - break - } else { - const guid = current["@guid"] - let structure = this.returnStructureByComponentGuid(guid) - - let newPt - if ((sourceRt && sourceRt != targetRt) || (!sourceRt && !incomingTargetRt)){ - newPt = await this.duplicateComponentGetId(guid, structure, rt, "last") - } else { - if (newPos < 0){ - newPt = await this.duplicateComponentGetId(guid, structure, rt, current.id) - } else { - newPt = await this.duplicateComponentGetId(guid, structure, rt, newComponent.id) - } + // console.log("work",work) + if (work) { + for (let ptId of work.ptOrder) { + let pt = JSON.parse(JSON.stringify(work.pt[ptId])) + /* + // + { + "@root": "http://id.loc.gov/ontologies/bibframe/title", + "http://id.loc.gov/ontologies/bibframe/title": [ + { + "@guid": "dYC2wKS5RkmPcP6rB7DH8m", + "@type": "http://id.loc.gov/ontologies/bibframe/Title", + "http://id.loc.gov/ontologies/bibframe/mainTitle": [ + { + "@guid": "th7wNkaXeBQxj4Gqc3PZKH", + "http://id.loc.gov/ontologies/bibframe/mainTitle": "Ch'ŏnggyech'ŏn Taehakch'ŏn ch'aekpang kŏri" + }, + { + "@guid": "49g8LszgTg4d6GzN7fBFhK", + "http://id.loc.gov/ontologies/bibframe/mainTitle": "청계천 대학천 책방 거리", + "@language": "ko-hang" + } + ] } - - newPt = newPt[0] - - profile["rt"][rt]["pt"][newPt].userValue = newComponent.userValue - profile["rt"][rt]["pt"][newPt].userModified = true - break - } + ] } - } - } - } - }, - - pasteSelected: async function(sourceRt=null){ + */ - let data - const clipboardContents = await navigator.clipboard.read(); + if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/title') { + let titleUserValue = pt.userValue + if (titleUserValue && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'] && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'].length > 0 && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0]) { + titleUserValue = titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0] + if (titleUserValue && titleUserValue["@type"] == "http://id.loc.gov/ontologies/bibframe/Title" && titleUserValue['http://id.loc.gov/ontologies/bibframe/mainTitle']) { - for (let item of clipboardContents){ - if (!item.types.includes("text/plain")) { - throw new Error("Clipboard does not contain text data."); - } - - let blob = await item.getType("text/plain") - const incomingValue = await blob.text() + let titleBnodeSorted = titleUserValue - data = incomingValue.split(";;;") - } - for (let item of data){ - const dataJson = JSON.parse(item) - if (!sourceRt){ - sourceRt = this.returnRtByGUID(dataJson['@guid']) - } - this.parseActiveInsert(JSON.parse(JSON.stringify(dataJson)), sourceRt) - } - }, + if (titleUserValue['http://id.loc.gov/ontologies/bibframe/mainTitle'].length > 1) { + // there are multiple titles, we want to make sure to pick the latin one + titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'] = this.sortObjectsByLatinMatch(titleUserValue['http://id.loc.gov/ontologies/bibframe/mainTitle'], 'http://id.loc.gov/ontologies/bibframe/mainTitle') + } + if (titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'].length > 0 && titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'][0] && titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle']) { + title = titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] + // there could be a non-latin title + if (titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle'].length > 1) { + for (let temp of titleBnodeSorted['http://id.loc.gov/ontologies/bibframe/mainTitle']) { + if (temp["@language"]) { + let lang = temp["@language"] + if (!lang.includes("-latin") && lang != "en") { + titleNonLatin = temp["http://id.loc.gov/ontologies/bibframe/mainTitle"] + } + } + } + } + } - //Check if the component's userValue is empty - isEmptyComponent: function(c){ - const component = c - const emptyArray = new Array("@root") - const userValue = JSON.parse(JSON.stringify(component["userValue"])) - - - // if there is only a @root - if (JSON.stringify(Object.keys(component.userValue)) == JSON.stringify(emptyArray)){ - return true - } else { - // if the children only have "@..." properties - for (let key in component.userValue){ - if (!key.startsWith("@")){ - let result = false - try{ - if (userValue[key].length == 0){ return true } - // this makes sure that the propertiesPanel will have the correct symbol when the incoming data - // has an populate electronicLocator - if (component.propertyURI != "http://id.loc.gov/ontologies/bibframe/electronicLocator"){ - result = Object.keys(userValue[key][0]).every((childKey) => childKey.startsWith("@")) - } else { - result = !Object.keys(userValue[key][0]).some((childKey) => childKey.startsWith("@id")) + } + if (titleUserValue && titleUserValue["@type"] == "http://id.loc.gov/ontologies/bibframe/Title" && titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum']) { + if (titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'].length > 0 && titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'][0] && titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'][0]['http://id.loc.gov/ontologies/bflc/nonSortNum']) { + titleNonSort = titleUserValue['http://id.loc.gov/ontologies/bflc/nonSortNum'][0]['http://id.loc.gov/ontologies/bflc/nonSortNum'] + } + } + } } - } catch(err) { - console.debug("error: Checking if component is empty, ", err) - } - return result - } - } - } - return false - }, - - /** - * Will look at the literals being used on the record and pick the most common script found - * used to help pick the correct auth labels to include in the access points, - * will set this.mostCommonNonLatinScript - * @return {String} - */ - setMostCommonNonLatinScript(){ - - let literals = this.returnAllNonLatinLiterals() - - // if (literals.length == 0){ - // literals = this.returnAllNonLatinLiterals(true) - // } - - let allScriptsFound = [] - for (let l of literals){ - if (l.node && l.node['@language'] && l.node['@language'].indexOf('-')>-1){ - let lang = l.node['@language'].split("-")[1].toLowerCase() - if (lang != 'latn'){ - allScriptsFound.push(lang) - } - } - } - - if (allScriptsFound.length>0){ - // get mode - let mostCommong = allScriptsFound.sort((a,b) => allScriptsFound.filter(v => v===a).length - allScriptsFound.filter(v => v===b).length).pop(); - // capitalize - mostCommong = String(mostCommong).charAt(0).toUpperCase() + String(mostCommong).slice(1) - this.mostCommonNonLatinScript=mostCommong - }else{ - this.mostCommonNonLatinScript = null - } - - return this.mostCommonNonLatinScript - }, + if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/contribution') { + let contributorUserValue = pt.userValue + let type = "normal" + if (contributorUserValue && contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'] && + contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'].length > 0 && + contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'][0] && + contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['@type']) { + + contributorUserValue = contributorUserValue['http://id.loc.gov/ontologies/bibframe/contribution'][0] + if (contributorUserValue && contributorUserValue["@type"] == "http://id.loc.gov/ontologies/bibframe/PrimaryContribution") { + type = "PrimaryContribution" + if (contributorUserValue['http://id.loc.gov/ontologies/bibframe/agent'] && + contributorUserValue['http://id.loc.gov/ontologies/bibframe/agent'][0]) { + let agent = contributorUserValue['http://id.loc.gov/ontologies/bibframe/agent'][0] + if (agent && agent['http://www.w3.org/2000/01/rdf-schema#label'] && agent['http://www.w3.org/2000/01/rdf-schema#label'].length > 0 && agent['http://www.w3.org/2000/01/rdf-schema#label'][0] && agent['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']) { + // console.log("agentagentagentagent",agent) + let agentData = { type: type, label: agent['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] } + if (agent['@id']) { + agentData['@id'] = agent['@id'] + } else { + agentData['@id'] = null + } + if (agent['@type']) { + agentData['@type'] = agent['@type'] + } else { + agentData['@type'] = null + } + + + if (agent['http://id.loc.gov/ontologies/bflc/marcKey']) { + agentData['http://id.loc.gov/ontologies/bflc/marcKey'] = agent['http://id.loc.gov/ontologies/bflc/marcKey'] + } else { + agentData['http://id.loc.gov/ontologies/bflc/marcKey'] = null + } + + contributors.push(agentData) + } + } + } + } + } - /** - * Return the URL to use for lookups for various types of lookups based on the active profile - * A way to get a URL to a lookup without having to hardcode it - * @param {string} property - the proerty short version name, like bf:language - * @return {String} - */ - returnProfileLookupUrl(property){ + if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject' && (firstSubject === null || secondSubject === null) && !pt.deleted) { + let subjectUserValue = pt.userValue + if (subjectUserValue && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'].length > 0 && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0] && (subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'] || subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'])) { + if (subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'].length > 0 && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']) { + if (firstSubject === null) { + firstSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] + } else { + secondSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] + } + } else if (subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'].length > 0 && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0] && subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']) { + if (firstSubject === null) { + firstSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] + } else { + secondSubject = subjectUserValue['http://id.loc.gov/ontologies/bibframe/subject'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] + } + } + } + } + } + } - for (let rt of this.activeProfile.rtOrder){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - let purl = utilsParse.namespaceUri(this.activeProfile.rt[rt].pt[pt].propertyURI) - if (purl == property){ - if (this.activeProfile.rt[rt].pt[pt].valueConstraint && this.activeProfile.rt[rt].pt[pt].valueConstraint.useValuesFrom && this.activeProfile.rt[rt].pt[pt].valueConstraint.useValuesFrom.length>0){ - return this.activeProfile.rt[rt].pt[pt].valueConstraint.useValuesFrom[0] + // look at the instance to get the subtitle + for (let rtId in this.activeProfile.rt) { + if (rtId.indexOf(":Instance") > -1) { + inst = this.activeProfile.rt[rtId] + if (inst) { break } + } } - } + if (inst) { + for (let ptId of inst.ptOrder) { + let pt = JSON.parse(JSON.stringify(inst.pt[ptId])) - } - } - - - // we didn't find it see if it has instead a templateref value - for (let rt of this.activeProfile.rtOrder){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - let purl = utilsParse.namespaceUri(this.activeProfile.rt[rt].pt[pt].propertyURI) - if (purl == property){ - if (this.activeProfile.rt[rt].pt[pt].valueConstraint && this.activeProfile.rt[rt].pt[pt].valueConstraint.valueTemplateRefs && this.activeProfile.rt[rt].pt[pt].valueConstraint.valueTemplateRefs.length>0){ - let lookForTemplate = this.activeProfile.rt[rt].pt[pt].valueConstraint.valueTemplateRefs[0] - for (let pName in this.profiles){ - if (this.profiles[pName].rtOrder.includes(lookForTemplate)){ - for (let p of this.profiles[pName].rt[lookForTemplate].ptOrder){ - let purl2 = utilsParse.namespaceUri(this.profiles[pName].rt[lookForTemplate].pt[p].propertyURI) - if (purl2 == property || purl2 == 'owl:sameAs'){ - if (this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint && this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint.useValuesFrom && this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint.useValuesFrom.length>0){ - return this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint.useValuesFrom[0] - } + if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/title') { + let titleUserValue = pt.userValue + + if (titleUserValue && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'] && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'].length > 0 && titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0]) { + titleUserValue = titleUserValue['http://id.loc.gov/ontologies/bibframe/title'][0] + if (titleUserValue && titleUserValue["@type"] == "http://id.loc.gov/ontologies/bibframe/Title" && titleUserValue['http://id.loc.gov/ontologies/bibframe/subtitle']) { + let sub = titleUserValue['http://id.loc.gov/ontologies/bibframe/subtitle'] + subtitle = sub[0]["http://id.loc.gov/ontologies/bibframe/subtitle"] + } + } } - } } - } } - } - } - } + if (subtitle && title) { + title = title.replace(": " + subtitle, "").trim() + } - return false - }, - - /** - * Builds and posts a Hub Stub - * - * @param {object} hubCreatorObj - obj with creator label, uri,marcKey - * @param {string} title - title string - * @param {string} langObj - {uri:"",label:""} - * @return {String} - */ - async buildPostHubStub(hubCreatorObj,title,variant,variantLanguage,langObj,catCode){ - - // console.log("hubCreatorObj",hubCreatorObj) - let xml = await utilsExport.createHubStubXML(hubCreatorObj,title,variant,variantLanguage,langObj,catCode) - - console.log(xml) - // let eid = 'e' + decimalTranslator.new() - let eid = 'e' + Date.now().toString() - // eid = eid.substring(0,8) - - // pass a fake activeprofile with id == Hub to trigger hub protocols - let pubResuts - try{ - pubResuts = await utilsNetwork.publish(xml, eid, {id: 'Hub'}) - - }catch (error){ - console.log(error) - alert("There was an error creating your Hub. Please report this issue.") - } - - // // to simulate a post - // alert("Fake Hub Requesting") - // pubResuts = { - // "name": "bc331009-aa60-48b3-ba17-865fc7389f23", - // "publish": { - // "status": "published" - // }, - // "postLocation": "http://preprod-8299.id.loc.gov/resources/hubs/bf110051-532b-c50c-5d5c-baa4ea6d2044" - // } - - if (pubResuts && pubResuts.status){ - let hubId = pubResuts.postLocation ? pubResuts.postLocation.split('/').pop() : null - this.logEvent('PUBLISHED_HUB', { metadata: [hubId] }) - } - - return pubResuts + if (pt && pt.userValue && pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'] && pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'].length > 0) { + let uv = pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] + if (uv && uv['@type'] && uv['@type'] == 'http://id.loc.gov/ontologies/bibframe/ClassificationLcc') { + // this is a LCC field then + // we need to gather info from the component and the rest of the work to build links/suggestions - }, - /** - * Look for the Statement of Responsibility in the record to add to the 670 $b - * - */ - nacoStubReturnSoR(){ - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Instance")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/responsibilityStatement"){ - if (pt.userValue - && pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'][0]['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'] - ){ - let sor = pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'][0]['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'] - - if (sor.endsWith(".")) { - sor = sor.slice(0, -1); - } - - return sor - } - } - } - } - } - return false - }, - checkCip(){ - /** - * CIP is signaled by - * - * Projected publication date (YYMM) is populated - * encoding level: prepublication - * 906 $e ecip - * - * in the instance. - * - * If either of the checks is true, flagging it as a CIP - */ - let isCip = false - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Instance")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - let userValue = pt.userValue - if (pt.propertyURI == "http://id.loc.gov/ontologies/bflc/projectedProvisionDate"){ - try { - let value = userValue["http://id.loc.gov/ontologies/bflc/projectedProvisionDate"][0]["http://id.loc.gov/ontologies/bflc/projectedProvisionDate"] - if (value){ - isCip = true - } - } catch { } - } - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/adminMetadata" && pt.adminMetadataType == 'primary'){ - try{ - let encodingLevel = userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bflc/encodingLevel"][0] - let code = encodingLevel["http://id.loc.gov/ontologies/bibframe/code"][0]["http://id.loc.gov/ontologies/bibframe/code"] - if (code == 8 || code == '8'){ - isCip = true - } - } catch { } - } - } - } - } - return isCip - }, + if (uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'] && uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'].length > 0 && uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0] && uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion']) { + classNumber = uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] + classGuid = uv['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['@guid'] - /** - * Retrieves the main title from the NACO stub work profile by traversing the resource template structure. - * Looks for a property with URI "http://id.loc.gov/ontologies/bibframe/title" and extracts its main title value. - * - * @returns {(string|false)} The main title string if found, false otherwise - * @access public - * @requires activeProfile - Profile must be loaded with valid RT structure - */ - nacoStubReturnMainTitle(){ - let subtitle = '' - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Instance")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/title"){ - if (pt.userValue - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] - ){ - // look for the one that is set as latin first, if we can find it - for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle']){ - if (aTitle['@language'] && aTitle['@language'].toLowerCase().indexOf('latn')>-1){ - subtitle = aTitle['http://id.loc.gov/ontologies/bibframe/subtitle'] } - } + if (uv['http://id.loc.gov/ontologies/bibframe/itemPortion'] && uv['http://id.loc.gov/ontologies/bibframe/itemPortion'].length > 0 && uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0] && uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion']) { + cutterNumber = uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion'] + cutterGuid = uv['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['@guid'] - // otherwise look for the first one that doesn't have a language tag - for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle']){ - if (!aTitle['@language']){ - subtitle = aTitle['http://id.loc.gov/ontologies/bibframe/subtitle'] } - } - // if we can't find one without a language tag, just return the first one - subtitle = pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] - } - } - } - } - } - - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Work")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/title"){ - if (pt.userValue - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] - ) - { - // look for the one that is set as latin first, if we can find it - for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle']){ - if (aTitle['@language'] && aTitle['@language'].toLowerCase().indexOf('latn')>-1){ - let title = aTitle['http://id.loc.gov/ontologies/bibframe/mainTitle'] - // remove the subtitle - if (title.includes(subtitle)){ - title = title.replace(subtitle, "") - title = title.split(" : ")[0] - } - return title - } - } - // otherwise look for the first one that doesn't have a language tag - for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle']){ - if (!aTitle['@language']){ - let title = aTitle['http://id.loc.gov/ontologies/bibframe/mainTitle'] - // remove the subtitle - if (title.includes(subtitle)){ - title = title.replace(subtitle, "") - title = title.split(" : ")[0] - } - return title + if (titleNonSort && titleNonSort.trim().length > 0 && title) { + if (isNaN(parseInt(titleNonSort)) == false) { + titleNonSort = parseInt(titleNonSort) + title = title.substr(titleNonSort).trim() + } } - } - // if we can't find one without a language tag, just return the first one - let title = pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] - - // remove the subtitle - if (title.includes(subtitle)){ - title = title.replace(subtitle, "") - title = title.split(" : ")[0] - } - return title - } - } - } - } - } - return false - }, + // console.log('classNumber',classNumber) + // console.log('cutterNumber',cutterNumber) + // console.log('work',work) + // console.log('title',title) + // console.log('titleNonSort',titleNonSort) + // console.log("contributors",contributors) + + return { + title: title, + subtitle: subtitle, + titleNonLatin: titleNonLatin, + classNumber: classNumber, + cutterNumber: cutterNumber, + titleNonSort: titleNonSort, + contributors: contributors, + firstSubject: firstSubject, + secondSubject: secondSubject, + cutterGuid: cutterGuid, + classGuid: classGuid + } - /** - * Retrieves the Library of Congress Control Number (LCCN) from the NACO stub profile. - * Searches through Instance resource templates for bibframe:identifiedBy property - * with type bibframe:Lccn. - * - * @returns {(string|false)} The LCCN string if found, false otherwise - * @access public - * @requires activeProfile - Profile must be loaded with valid RT structure - */ - nacoStubReturnLCCN(){ - - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Instance")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/identifiedBy"){ - if (pt.userValue - && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['@type'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/Lccn" - ) - return pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'].trim() - } - } - } - } - return false - }, - /** - * Retrieves the date from the profile by searching multiple date fields. - * Searches in order: - * 1. Instance provision activity simple date - * 2. Instance provision activity EDTF date - * 3. Work origin date - * - * @returns {(string|false)} The date string if found in any of the searched fields, false otherwise - * @access public - * @requires activeProfile - Profile must be loaded with valid RT structure - */ - nacoStubReturnDate(){ - - let provisionActivityEDTFDate = null - let provisionActivitySimpleDate = null - let originDate = null - - for (let rt of this.activeProfile.rtOrder){ - // look in the instance first for provision activity - if (rt.indexOf(":Instance")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/provisionActivity"){ - if (pt.userValue - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'] - ){ - provisionActivitySimpleDate = pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'] - } - } - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/provisionActivity"){ - if (pt.userValue - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'][0]['http://id.loc.gov/ontologies/bibframe/date'] - ){ - provisionActivityEDTFDate = pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'][0]['http://id.loc.gov/ontologies/bibframe/date'] - } - } - - } - } - if (rt.indexOf(":Work")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/originDate"){ - if (pt.userValue - && pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'][0] - && pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'][0]['http://id.loc.gov/ontologies/bibframe/originDate'] - ){ - originDate = pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'][0]['http://id.loc.gov/ontologies/bibframe/originDate'] + } else { + // This is a LCC field, it shouldn't return `false`. False causes things to disappear + // console.log("RETRUN FA:LSE 2") + return { + title: null, + subtitle: null, + titleNonLatin: null, + classNumber: null, + cutterNumber: null, + titleNonSort: null, + contributors: [], + firstSubject: null, + secondSubject: null, + cutterGuid: null, + classGuid: null + } } - } - } - } - } - let useDate = false - if (provisionActivitySimpleDate){ - useDate = provisionActivitySimpleDate - }else if(provisionActivityEDTFDate){ - useDate = provisionActivityEDTFDate - }else if (originDate){ - useDate = originDate - }else{ - useDate = false - } - if (useDate){ - useDate = useDate.replace(/\[/g, '').replace(/\]/g, '') // remove brackets if they exist - useDate = useDate.replace(/{/g, '').replace(/}/g, '') // remove brackets if they exist + } else { + if (pt && (pt.userValue && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/classification' || pt.userValue && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/relation') && Object.keys(pt.userValue).length == 1) { - useDate = useDate.trim() - } + if (titleNonSort && titleNonSort.trim().length > 0 && title) { + if (isNaN(parseInt(titleNonSort)) == false) { + titleNonSort = parseInt(titleNonSort) + title = title.substr(titleNonSort).trim() + } + } + // it is a new record, so there is no info but the LCC classification is by default so populate the other stuff + return { + title: title, + subtitle: subtitle, + titleNonLatin: titleNonLatin, + classNumber: null, + cutterNumber: null, + titleNonSort: titleNonSort, + contributors: contributors, + firstSubject: firstSubject, + secondSubject: secondSubject, + cutterGuid: null, + classGuid: null + } - return useDate + } else if (pt && (pt.userValue && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/expressionOf')) { + return { + title: title, + subtitle: subtitle, + titleNonLatin: titleNonLatin, + classNumber: null, + cutterNumber: null, + titleNonSort: null, + contributors: contributors, + firstSubject: null, + secondSubject: null, + cutterGuid: null, + classGuid: null + } + } - }, + } - /** - * Retrieves the Work URI from the NACO stub profile by searching through resource templates. - * Returns the first URI found in a resource template containing ":Work" in its name. - * - * @returns {(string|false)} The Work URI if found, false otherwise - * @access public - * @requires activeProfile - Profile must be loaded with valid RT structure - */ - nacoStubReturnWorkURI(){ - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Work")>-1){ - if (this.activeProfile.rt[rt].URI){ - return this.activeProfile.rt[rt].URI - } - } - } - return false - }, - /** - * Retrieves the Work Instance from the NACO stub profile by searching through resource templates. - * Returns the first URI found in a resource template containing ":Work" in its name. - * - * @returns {(string|false)} The Work URI if found, false otherwise - * @access public - * @requires activeProfile - Profile must be loaded with valid RT structure - */ - nacoStubReturnInstanceURI(){ - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Instance")>-1){ - if (this.activeProfile.rt[rt].URI){ - return this.activeProfile.rt[rt].URI - } - } - } - return false - }, - nacoStubReturnPopulatedValue(guid){ - let pt = utilsProfile.returnPt(this.activeProfile,guid) - let URI = null - let marcKey = null - let marcKey4xx = null + //ClassificationLcc + // console.log("RETRUN FA:LSE 3") + return false - if (pt && - pt.userValue && - pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent'] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent'][0]){ - console.log("pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent']",pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent']) - let agent = pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent'][0] - if (agent && agent['@id']){ - URI = agent['@id'] - } - if (agent && agent['http://id.loc.gov/ontologies/bflc/marcKey'] && - agent['http://id.loc.gov/ontologies/bflc/marcKey'][0] && - agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] - ){ - marcKey = agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] - } + }, - if (agent && agent['http://id.loc.gov/ontologies/bflc/marcKey'] && - agent['http://id.loc.gov/ontologies/bflc/marcKey'][1] && - agent['http://id.loc.gov/ontologies/bflc/marcKey'][1]['http://id.loc.gov/ontologies/bflc/marcKey'] - ){ - marcKey4xx = agent['http://id.loc.gov/ontologies/bflc/marcKey'][1]['http://id.loc.gov/ontologies/bflc/marcKey'] - } + /** + * Walks the active profile and extracts subject and genreForm labels/URIs, + * then looks up classification URIs for each subject + * @return {object} - {subjects: [], genreForms: []} + */ + mlcNumberEnhance: async function () { + + let subjects = [] + let genreForms = [] + + for (let rtId in this.activeProfile.rt) { + if (rtId.indexOf(":Work") > -1) { + for (let ptId of this.activeProfile.rt[rtId].ptOrder) { + let pt = this.activeProfile.rt[rtId].pt[ptId] + + // collect subjects + if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject' && !pt.deleted) { + let uv = pt.userValue + if (uv && uv['http://id.loc.gov/ontologies/bibframe/subject'] && uv['http://id.loc.gov/ontologies/bibframe/subject'].length > 0 && uv['http://id.loc.gov/ontologies/bibframe/subject'][0]) { + let subj = uv['http://id.loc.gov/ontologies/bibframe/subject'][0] + let label = null + let uri = null + if (subj['http://www.w3.org/2000/01/rdf-schema#label'] && subj['http://www.w3.org/2000/01/rdf-schema#label'].length > 0 && subj['http://www.w3.org/2000/01/rdf-schema#label'][0] && subj['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']) { + label = subj['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] + } else if (subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] && subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'].length > 0 && subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0] && subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']) { + label = subj['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] + } + // get URI: top-level @id, or first componentList entry's @id + if (subj['@id']) { + uri = subj['@id'] + } else if (subj['http://www.loc.gov/mads/rdf/v1#componentList'] && subj['http://www.loc.gov/mads/rdf/v1#componentList'].length > 0 && subj['http://www.loc.gov/mads/rdf/v1#componentList'][0] && subj['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id']) { + uri = subj['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id'] + } + if (label) { + subjects.push({ label: label, uri: uri }) + } + } + } + // collect genreForms + if (pt && pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/genreForm' && !pt.deleted) { + let uv = pt.userValue + if (uv && uv['http://id.loc.gov/ontologies/bibframe/genreForm'] && uv['http://id.loc.gov/ontologies/bibframe/genreForm'].length > 0 && uv['http://id.loc.gov/ontologies/bibframe/genreForm'][0]) { + let gf = uv['http://id.loc.gov/ontologies/bibframe/genreForm'][0] + let label = null + let uri = null + if (gf['http://www.w3.org/2000/01/rdf-schema#label'] && gf['http://www.w3.org/2000/01/rdf-schema#label'].length > 0 && gf['http://www.w3.org/2000/01/rdf-schema#label'][0] && gf['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label']) { + label = gf['http://www.w3.org/2000/01/rdf-schema#label'][0]['http://www.w3.org/2000/01/rdf-schema#label'] + } else if (gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] && gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'].length > 0 && gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0] && gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']) { + label = gf['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'][0]['http://www.loc.gov/mads/rdf/v1#authoritativeLabel'] + } + // get URI: top-level @id, or first componentList entry's @id + if (gf['@id']) { + uri = gf['@id'] + } else if (gf['http://www.loc.gov/mads/rdf/v1#componentList'] && gf['http://www.loc.gov/mads/rdf/v1#componentList'].length > 0 && gf['http://www.loc.gov/mads/rdf/v1#componentList'][0] && gf['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id']) { + uri = gf['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id'] + } + if (label) { + genreForms.push({ label: label, uri: uri }) + } + } + } + } + } + } + // look up classification for the first subject only + let useLCC = null + if (subjects.length > 0 && subjects[0].uri) { + let classification = await utilsNetwork.getSubjectClassification(subjects[0].uri) + if (classification && classification.length > 0) { + useLCC = classification.charAt(0) + } + } + // determine P-class from genreForms + // URIs that indicate useP = true (fiction-related) + let pClassTrueURIs = [ + 'http://id.loc.gov/authorities/genreForms/gf2014026339', + 'http://id.loc.gov/authorities/genreForms/gf2014026243', + 'http://id.loc.gov/authorities/genreForms/gf2014026259', + 'http://id.loc.gov/authorities/genreForms/gf2018026099', + 'http://id.loc.gov/authorities/genreForms/gf2015026019', + 'http://id.loc.gov/authorities/genreForms/gf2014026456', + 'http://id.loc.gov/authorities/genreForms/gf2015026020', + 'http://id.loc.gov/authorities/genreForms/gf2014026518', + 'http://id.loc.gov/authorities/genreForms/gf2014026542', + 'http://id.loc.gov/authorities/genreForms/gf2014026559' + ] + // URIs that map to specific letter codes (also useP = true) + let pClassLetterURIs = { + 'http://id.loc.gov/authorities/genreForms/gf2014026297': 'd', + 'http://id.loc.gov/authorities/genreForms/gf2014026094': 'e', + 'http://id.loc.gov/authorities/genreForms/gf2015026020': 'f', + 'http://id.loc.gov/authorities/genreForms/gf2014026110': 'h', + 'http://id.loc.gov/authorities/genreForms/gf2014026141': 'i', + 'http://id.loc.gov/authorities/genreForms/gf2014026054': 'i', + 'http://id.loc.gov/authorities/genreForms/gf2014026542': 'j', + 'http://id.loc.gov/authorities/genreForms/gf2014026488': 'p', + 'http://id.loc.gov/authorities/genreForms/gf2014026481': 'p', + 'http://id.loc.gov/authorities/genreForms/gf2011026363': 's' + } + + let useP = false + for (let gf of genreForms) { + // check URI matches + if (gf.uri && pClassTrueURIs.includes(gf.uri)) { + useP = true + break + } + if (gf.uri && pClassLetterURIs[gf.uri]) { + useP = true + break + } + // check label patterns + let label = (gf.label || '').toLowerCase() + if (label.includes('fiction') && !label.includes('onfiction')) { + useP = true + break + } + if ((gf.label || '').includes('(Fiction)')) { + useP = true + break + } + if (label.includes('humor')) { + useP = true + break + } + if (label.includes('poetry')) { + useP = true + break + } + if (label.includes('speeches')) { + useP = true + break + } + } - } + return { + subjects: subjects, + genreForms: genreForms, + useP: useP, + useLCC: useLCC + } - if (pt && - pt.userValue && - pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]){ + }, - let agent = pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0] - if (agent && agent['@id']){ - URI = agent['@id'] + /** + * Check if, and which, up down buttons should display in the action button + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @return {array} - the first value says if the "up" button should display, the second if the "down" + */ + showUpDownButtons: function (componentGuid) { + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + + let target + let targetType + + let items + let subjItems = [] + let contribItems = [] + let gfItems = [] + let idItems = [] + + if (pt !== false) { + let workRtId = null + for (let rtId in this.activeProfile.rt) { + if (rtId.indexOf(":Work") > -1) { + workRtId = rtId + for (let ptId of this.activeProfile.rt[rtId].ptOrder) { + if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject') { + subjItems.push(ptId) + if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid) { + target = ptId + targetType = "subject" + } + } + if ( + this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/contribution' && + this.activeProfile.rt[rtId].pt[ptId].propertyLabel != "Creator of Work" + ) { + contribItems.push(ptId) + if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid) { + target = ptId + targetType = "contribution" + } + } + if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/genreForm') { + gfItems.push(ptId) + if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid) { + target = ptId + targetType = "gf" + } + } + } + } + if (rtId.indexOf(":Instance") > -1) { + for (let ptId of this.activeProfile.rt[rtId].ptOrder) { + if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/identifiedBy') { + idItems.push(ptId) + if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid) { + target = ptId + targetType = "identifier" + } + } + } + } + } } - if (agent && agent['http://id.loc.gov/ontologies/bflc/marcKey'] && - agent['http://id.loc.gov/ontologies/bflc/marcKey'][0] && - agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] - ){ - marcKey = agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] + + if (targetType == "subject") { + items = subjItems + } else if (targetType == "gf") { + items = gfItems + } else if (targetType == "identifier") { + items = idItems + } else { + items = contribItems } - } + if (items.length <= 1) { + return [false, false] + } else { + let pos = items.indexOf(target) + if (pos == 0) { + return [false, true] + } else if (pos == items.length - 1) { + return [true, false] + } else if (pos < 0) { + return [false, false] + } else { + return [true, true] + } + } + }, - return { - URI: URI, - marcKey: marcKey, - marcKey4xx: marcKey4xx - } + //This is repurposed from `makeSubjectHeadingPrimary` + /** + * Moves the selected heading up or down + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} dir - which way the item should move `up` or `down` + * @return {void} + */ + moveUpDown: function (componentGuid, dir) { + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + let target + + if (pt !== false) { + let firstHeading = null + let workRtId = null + let instRtId = null + for (let rtId in this.activeProfile.rt) { + if (rtId.indexOf(":Work") > -1) { + for (let ptId of this.activeProfile.rt[rtId].ptOrder) { + if ( + this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject' || + this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/contribution' || + this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/genreForm' + ) { + if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid) { + target = ptId + workRtId = rtId + } + } + } + } - }, + if (rtId.indexOf(":Instance") > -1) { + for (let ptId of this.activeProfile.rt[rtId].ptOrder) { + if ( + this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/identifiedBy' + ) { + if (this.activeProfile.rt[rtId].pt[ptId]["@guid"] == componentGuid) { + target = ptId + instRtId = rtId + } + } + } + } + } - nacoStubReturn245(guid){ - let subfieldA = "" - let subfieldB = "" - let subfieldC = "" - - for (let rt of this.activeProfile.rtOrder){ - if (rt.indexOf(":Instance")>-1){ - for (let pt of this.activeProfile.rt[rt].ptOrder){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyLabel == "Title information"){ - let userValue = pt.userValue - if ( - userValue['http://id.loc.gov/ontologies/bibframe/title'] && - userValue['http://id.loc.gov/ontologies/bibframe/title'][0] && - userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] - ){ - let titleComp = userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] - subfieldA = titleComp[0]["http://id.loc.gov/ontologies/bibframe/mainTitle"] - } - - if ( - userValue['http://id.loc.gov/ontologies/bibframe/title'] && - userValue['http://id.loc.gov/ontologies/bibframe/title'][0] && - userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] - ){ - let titleComp = userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] - subfieldB = titleComp[0]["http://id.loc.gov/ontologies/bibframe/subtitle"] - } - } - } - } - } + if (target) { + let targetRt = null + if (workRtId) { + targetRt = workRtId + } else if (instRtId) { + targetRt = instRtId + } + let currentPos = this.activeProfile.rt[targetRt].ptOrder.indexOf(target) + let newPos + if (dir == "up") { + newPos = currentPos - 1 + } else { + newPos = currentPos + 1 + } - subfieldC = this.nacoStubReturnSoR() + //swap the target with the element in the desired position + //delete from current pos + this.activeProfile.rt[targetRt].ptOrder.splice(currentPos, 1) + //put in it's new position + this.activeProfile.rt[targetRt].ptOrder.splice(newPos, 0, target) - return { - subA: subfieldA, - subB: subfieldB, - subC: subfieldC - } + this.dataChanged() + } + } + }, - }, + /** + * Moves the passed heading the first of the subjects in the PT order + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @return {void} + */ + makeSubjectHeadingPrimary: async function (componentGuid) { + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + if (pt !== false) { - /** - * Builds and posts a NACO Stub - * - * @param {object} hubCreatorObj - obj with creator label, uri,marcKey - * @param {string} title - title string - * @param {string} langObj - {uri:"",label:""} - * @return {String} - */ - async buildNacoStub(oneXX,fourXX,mainTitle,workURI, mainTitleDate, mainTitleLccn, mainTitleNote,zero46,add667,extraMarcStatements,useAdvancedMode){ - console.log(oneXX,fourXX,mainTitle,workURI,zero46) - let lccn = await utilsNetwork.nacoLccn() - if (lccn){ - this.logEvent('NACO_LCCN_ISSUED', { metadata: [lccn] }) - } - let NARData = await utilsExport.createNacoStubXML(oneXX,fourXX,mainTitle,lccn,workURI, mainTitleDate, mainTitleLccn, mainTitleNote,zero46,add667,extraMarcStatements,useAdvancedMode) - NARData.lccn = lccn - return NARData - }, + // loop through all the headings and find the place the headings start + let firstHeading = null + let workRtId = null + for (let rtId in this.activeProfile.rt) { + if (rtId.indexOf(":Work") > -1) { + workRtId = rtId + for (let ptId of this.activeProfile.rt[rtId].ptOrder) { + if (this.activeProfile.rt[rtId].pt[ptId].propertyURI == 'http://id.loc.gov/ontologies/bibframe/subject') { + firstHeading = ptId + break + } + } + } + if (firstHeading) { break } + } - async postNacoStub(xml,lccn){ + if (firstHeading) { - let pubResuts + let firstHeadingPos = this.activeProfile.rt[workRtId].ptOrder.indexOf(firstHeading) + let currentHeadingPos = this.activeProfile.rt[workRtId].ptOrder.indexOf(pt.id) - try{ - pubResuts = await utilsNetwork.publishNar(xml) - }catch (error){ - console.log(error) - alert("There was an error creating your NAR. Please report this issue.") - } + // remove the current heading from its position + this.activeProfile.rt[workRtId].ptOrder.splice(currentHeadingPos, 1); + // insert where the first heading is + this.activeProfile.rt[workRtId].ptOrder.splice(firstHeadingPos, 0, pt.id); - // pubResuts = {'postLocation': 'https://id.loc.gov/authorities/names/n83122656', status: 'published'} - console.log('pubResuts') - console.log(pubResuts) + this.dataChanged() + } + } + }, - if (pubResuts && pubResuts.status === 'published'){ - this.logEvent('PUBLISHED_NAR', { metadata: [lccn] }) - }else if (pubResuts && pubResuts.status === true){ - this.logEvent('PUBLISHED_NAR', { metadata: [lccn] }) - } - return { - xml: xml, - pubResuts: pubResuts, - lccn: lccn - } + triggerBadXMLBuildRecovery: function (lastGoodBuild, lastGoodBuildTimetamp) { + this.showRecoveryModal = true + const timeAgo = new TimeAgo('en-US') + this.activeProfile = JSON.parse(JSON.stringify(lastGoodBuild)) + this.dataChanged() + }, - }, + breakRecord: async function (componentGuid, structure) { - /** Add the DDC to marva - * - * @param {object} deweyInfo - The dewey information that will be inserted into Marva - * @param {string} guid - The GUID of the LCC component that will be used to create the new component - * @param {string} structure - the structure that will be used to create the new component - */ - addDdc: async function(deweyInfo, guid, structure){ - //Look to see if there is a DDC component - let activeProfile = this.activeProfile - let hasEmptyDDC = false - let ddcComponent = null - let lastClassifiction = null - let newDDC - - for (let pt in activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt){ - if (pt.includes("id_loc_gov_ontologies_bibframe_classification__classification_numbers")){ - const target = activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt[pt] - const userValue = target.userValue - const classification = userValue["http://id.loc.gov/ontologies/bibframe/classification"][0] - const type = classification["@type"] - if (!pt.deleted){ - lastClassifiction = pt - //type == "http://id.loc.gov/ontologies/bibframe/ClassificationDdc" && - if (!Object.keys(classification).includes("http://id.loc.gov/ontologies/bibframe/classificationPortion")){ - // if (type == "http://id.loc.gov/ontologies/bibframe/ClassificationDdc"){ - hasEmptyDDC = true - ddcComponent = classification - newDDC = [target.id] - } - } - } - } - - // if no empty ddc, create one - if (!hasEmptyDDC){ - newDDC = await this.duplicateComponentGetId(this.returnStructureByComponentGuid(guid)['@guid'], structure, "lc:RT:bf2:Monograph:Work", lastClassifiction) - ddcComponent = activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt[newDDC[0]] - } - - //add information to component - let userValue = null - try{ - userValue = ddcComponent.userValue["http://id.loc.gov/ontologies/bibframe/classification"][0] - } catch { - userValue = ddcComponent - } - - let dewey = null - if (Object.keys(deweyInfo).includes("DDC")){ - dewey = deweyInfo.DDC - } else { - dewey = deweyInfo.dewey - } - const newGuid = short.generate() - userValue["@type"] = "http://id.loc.gov/ontologies/bibframe/ClassificationDdc" - userValue["http://id.loc.gov/ontologies/bibframe/classificationPortion"] = [{ "@guid": newGuid, "http://id.loc.gov/ontologies/bibframe/classificationPortion": String(dewey) }] - - //Add the defaults: - const newComponent = activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt[newDDC[0]] - - // look up one level & use the appropriate structure - let parentStructure = this.returnStructureByComponentGuid(newComponent['@guid']) - if (parentStructure.valueConstraint && parentStructure.valueConstraint.valueTemplateRefs && parentStructure.valueConstraint.valueTemplateRefs.length>0){ - for (let vRt of parentStructure.valueConstraint.valueTemplateRefs){ - if (this.rtLookup[vRt] && vRt == "lc:RT:bf2:DDC"){ - for (let pt of this.rtLookup[vRt].propertyTemplates){ - if (pt.valueConstraint.defaults && pt.valueConstraint.defaults.length > 0){ - this.insertDefaultValuesComponent(newComponent['@guid'], pt) - } - } - } - } - } - }, + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - /** - * Insert a subject heading from the Yoshino recommendation pipeline into the active profile. - * Finds or creates an empty subject component in the Work RT and populates it. - * - * @param {string} label - The subject heading label - * @param {string} source - The vocabulary source name (e.g. "Library of Congress Subject Headings") - * @param {array} components - Parsed component data from the RDF [{label, uri, type, marcKey}, ...] - * @param {string|null} uri - Top-level subject URI (e.g. from rdf:about on the subject element) - * @param {string|null} marcKey - Top-level marcKey for the whole heading - */ - yoshinoInsertSubject: async function(label, source, components, uri, marcKey) { - let activeProfile = this.activeProfile - let workRt = null - let emptySubjectPt = null - let lastSubjectPt = null - console.log("label, source, components, uri, marcKey", label, source, components, uri, marcKey) - // Find the Work RT - for (let rt of activeProfile.rtOrder) { - if (rt.indexOf(':Work') > -1) { - workRt = rt - break - } - } - if (!workRt) return - - // Find an empty subject component or the last subject component - for (let ptId of activeProfile.rt[workRt].ptOrder) { - let pt = activeProfile.rt[workRt].pt[ptId] - if (pt && pt.propertyURI === 'http://id.loc.gov/ontologies/bibframe/subject' && !pt.deleted) { - lastSubjectPt = pt - let uv = pt.userValue - if (!pt.hasData || !uv || - !uv['http://id.loc.gov/ontologies/bibframe/subject'] || - uv['http://id.loc.gov/ontologies/bibframe/subject'].length === 0 || - !uv['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type']) { - emptySubjectPt = pt - } - } - } - - // If no empty subject component, duplicate the last one to create a new empty one - let targetPt = emptySubjectPt - if (!targetPt && lastSubjectPt) { - let newGuid = await this.duplicateComponent(lastSubjectPt['@guid'], this.returnStructureByGUID(lastSubjectPt['@guid'])) - if (newGuid) { - targetPt = this.returnStructureByGUID(newGuid) - } - } - - if (!targetPt) return - - // Build the userValue - let subjectValue = { - '@guid': translator.new(), - 'http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme': [{ - '@guid': translator.new(), - '@id': 'http://id.loc.gov/authorities/subjects' - }], - 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': [{ - '@guid': translator.new(), - 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': label - }], - 'http://www.w3.org/2000/01/rdf-schema#label': [{ - '@guid': translator.new(), - 'http://www.w3.org/2000/01/rdf-schema#label': label - }], - } - - // Set top-level @id if provided (the URI of the whole subject heading) - if (uri) { - subjectValue['@id'] = uri - } - - // Set top-level marcKey if provided - if (marcKey) { - subjectValue['http://id.loc.gov/ontologies/bflc/marcKey'] = [{ - '@guid': translator.new(), - 'http://id.loc.gov/ontologies/bflc/marcKey': marcKey - }] - } - - // Use rich component data if available for complex subjects - if (components && components.length > 1) { - // subjectValue['@type'] = 'madsrdf:ComplexSubject' - subjectValue['@type'] = 'bf:Topic' - subjectValue["http://www.w3.org/2000/01/rdf-schema#type"] = [{ - "@guid": short.generate(), - "@id": "http://www.loc.gov/mads/rdf/v1#ComplexSubject" - }] - subjectValue['http://www.loc.gov/mads/rdf/v1#componentList'] = components.map(c => { - let comp = { - '@guid': translator.new(), - '@type': c.type || 'http://www.loc.gov/mads/rdf/v1#Topic', - 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': [{ - '@guid': translator.new(), - 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': c.label - }] - } - if (c.uri) { - comp['@id'] = c.uri - } - if (c.marcKey) { - comp['http://id.loc.gov/ontologies/bflc/marcKey'] = [{ - '@guid': translator.new(), - 'http://id.loc.gov/ontologies/bflc/marcKey': c.marcKey - }] - } - return comp - }) - } else if (components && components.length === 1) { - // Solo subject with rich data - type from component - subjectValue['@type'] = components[0].type || 'http://www.loc.gov/mads/rdf/v1#Topic' - } else if (label.includes('--')) { - // Fallback: parse from label if no component data - // subjectValue['@type'] = 'madsrdf:ComplexSubject' - subjectValue['@type'] = 'bf:Topic' - subjectValue["http://www.w3.org/2000/01/rdf-schema#type"] = [{ - "@guid": short.generate(), - "@id": "http://www.loc.gov/mads/rdf/v1#ComplexSubject" - }] - let parts = label.split('--').map(s => s.trim()) - subjectValue['http://www.loc.gov/mads/rdf/v1#componentList'] = parts.map(part => ({ - '@guid': translator.new(), - '@type': 'http://www.loc.gov/mads/rdf/v1#Topic', - 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': [{ - '@guid': translator.new(), - 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': part - }] - })) - } else { - subjectValue['@type'] = 'http://www.loc.gov/mads/rdf/v1#Topic' - } - - // Add source - if (source && (source.toLowerCase().includes('lcsh') || source.toLowerCase().includes('library of congress subject'))) { - subjectValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ - '@guid': translator.new(), - '@type': 'http://id.loc.gov/ontologies/bibframe/Source', - '@id': 'http://id.loc.gov/vocabulary/subjectSchemes/lcsh', - 'http://www.w3.org/2000/01/rdf-schema#label': [{ - '@guid': translator.new(), - 'http://www.w3.org/2000/01/rdf-schema#label': 'Library of Congress subject headings' - }] - }] - } else if (source && source.toLowerCase().includes('children')) { - subjectValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ - '@guid': translator.new(), - '@type': 'http://id.loc.gov/ontologies/bibframe/Source', - '@id': 'http://id.loc.gov/vocabulary/subjectSchemes/cyac', - 'http://www.w3.org/2000/01/rdf-schema#label': [{ - '@guid': translator.new(), - 'http://www.w3.org/2000/01/rdf-schema#label': source - }] - }] - } else if (source && source.toLowerCase().includes('mesh')) { - subjectValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ - '@guid': translator.new(), - '@type': 'http://id.loc.gov/ontologies/bibframe/Source', - '@id': 'http://id.loc.gov/vocabulary/subjectSchemes/mesh', - 'http://www.w3.org/2000/01/rdf-schema#label': [{ - '@guid': translator.new(), - 'http://www.w3.org/2000/01/rdf-schema#label': source - }] - }] - } - - // Set the userValue - targetPt.userValue = { - '@guid': targetPt.userValue?.['@guid'] || translator.new(), - '@root': 'http://id.loc.gov/ontologies/bibframe/subject', - 'http://id.loc.gov/ontologies/bibframe/subject': [subjectValue] - } - - console.log('--- Yoshino: Insert Subject ---') - console.log('Label:', label) - console.log('Source:', source) - console.log('Top-level URI:', uri) - console.log('Top-level marcKey:', marcKey) - console.log('Components:', JSON.parse(JSON.stringify(components || []))) - console.log('Built userValue:', JSON.parse(JSON.stringify(targetPt.userValue))) - - targetPt.hasData = true - targetPt.userModified = true - targetPt.dataLoaded = false - - this.dataChanged() - }, + if (pt !== false) { - yoshinoInsertClassification: async function(classification) { - let activeProfile = this.activeProfile - let workRt = null - let emptyPt = null - let lastPt = null + let baseURI = pt.propertyURI + if (!pt.userValue[baseURI]) { + pt.userValue[baseURI] = [{}] + } + let userValue = JSON.parse(JSON.stringify(pt.userValue[baseURI][0])) - for (let rt of activeProfile.rtOrder) { - if (rt.indexOf(':Work') > -1) { - workRt = rt - break - } - } - if (!workRt) return - - for (let ptId of activeProfile.rt[workRt].ptOrder) { - let pt = activeProfile.rt[workRt].pt[ptId] - if (pt && pt.propertyURI === 'http://id.loc.gov/ontologies/bibframe/classification' && !pt.deleted) { - lastPt = pt - let uv = pt.userValue - if (!pt.hasData || !uv || - !uv['http://id.loc.gov/ontologies/bibframe/classification'] || - uv['http://id.loc.gov/ontologies/bibframe/classification'].length === 0 || - !uv['http://id.loc.gov/ontologies/bibframe/classification'][0]['@type']) { - emptyPt = pt - } - } - } + userValue['somting'] = { '@guid': '00000', 'hppts:sfsdfgfdsg.com': ['helllerrlooo'] } + pt.userValue[baseURI][0] = JSON.parse(JSON.stringify(userValue)) + // they changed something + this.dataChanged() - let targetPt = emptyPt - if (!targetPt && lastPt) { - let newGuid = await this.duplicateComponent(lastPt['@guid'], this.returnStructureByGUID(lastPt['@guid'])) - if (newGuid) { - targetPt = this.returnStructureByGUID(newGuid) - } - } - if (!targetPt) return - - let classValue = { - '@guid': translator.new(), - '@type': classification.type, - 'http://id.loc.gov/ontologies/bibframe/classificationPortion': [{ - '@guid': translator.new(), - 'http://id.loc.gov/ontologies/bibframe/classificationPortion': classification.portion - }] - } - - if (classification.sourceCode) { - classValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ - '@guid': translator.new(), - '@type': 'http://id.loc.gov/ontologies/bibframe/Source', - 'http://id.loc.gov/ontologies/bibframe/code': [{ - '@guid': translator.new(), - 'http://id.loc.gov/ontologies/bibframe/code': classification.sourceCode - }] - }] - } - - if (classification.edition) { - classValue['http://id.loc.gov/ontologies/bibframe/edition'] = [{ - '@guid': translator.new(), - 'http://id.loc.gov/ontologies/bibframe/edition': classification.edition - }] - } - - targetPt.userValue = { - '@guid': targetPt.userValue?.['@guid'] || translator.new(), - '@root': 'http://id.loc.gov/ontologies/bibframe/classification', - 'http://id.loc.gov/ontologies/bibframe/classification': [classValue] - } - - targetPt.hasData = true - targetPt.userModified = true - targetPt.dataLoaded = false - - this.dataChanged() - }, + } - /** Add a component to the library - * - * @param {string} guid - The GUID of the component - */ - addToComponentLibrary: async function(guid){ - let structure = JSON.parse(JSON.stringify(this.returnStructureByComponentGuid(guid))) - - // clean up component property values for storage - structure['@guid'] = null - // does the id end with a number, if so it is a duplicated component, or one of multiple, so remove that value - let lastIdPart = structure['id'].split("_").slice(-1)[0] - if (lastIdPart >= '0' && lastIdPart <= '9') { - // it is a number - let newId = structure['id'].split("_") - newId=newId.slice(0, -1) - newId=newId.join("_") - structure['id'] = newId - } - - let label = prompt("What to call this component?", structure.propertyLabel) - if (!label){ - return false - } - - if (structure['parentId'].includes(":Item")){ - let key = structure['parentId'] - if (key && key.includes(":Item")){ - if (key.includes("-") || key.includes("_")){ - let idx - idx = key.indexOf("_") - if (idx < 0){ - idx = key.indexOf("-") - } - key = key.slice(0, idx) - } - } - structure['parentId'] = key - } + }, + + /** + * Replace a placeholder in the default value when appropriate. + * Current replacements: `[$date]` with today's date + * + * @param {string} defaultString - the incoming string with a value that will be replaced. */ + replaceDefaultPlaceHolder: function (defaultString) { + if (!defaultString) { return defaultString } + let target = defaultString.match(/\[\$(.*)\]/g) + if (!target) { return defaultString } + + if (target[0].includes("date")) { + let date = new Date() + let dd = String(date.getDate()).padStart(2, '0') + let mm = String(date.getMonth() + 1).padStart(2, '0') + let yyyy = date.getFullYear() + + let today = yyyy + mm + dd + + return defaultString.replace("[$date]", today) + } // else if (target[0].includes("...")) + }, + /** + * Set the default values of the component fields + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {object} structure - passed from the UI, the structure object + * @param {object} pp - propertyPath for the structure. There's some nested components with defaults that won't build correctly otherwise + * @return {void} + */ + + insertDefaultValuesComponent: async function (componentGuid, structure, pp = false) { + // console.log(componentGuid) + // console.log("structure",structure) + + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + + //Delete related items from the cache, loading from the cache + // sometimes causes errors after inserting defaults + if (Object.keys(cachePt).includes(componentGuid)) { + delete cachePt[componentGuid] + } + for (let guid of Object.keys(cacheGuid)) { + try { + cleanCacheGuid(cacheGuid, JSON.parse(JSON.stringify(pt.userValue)), guid) + } catch { + console.warn("Couldn't clear the CacheGuid") + } + } - if (!this.componentLibrary.profiles[structure['parentId']]){ - this.componentLibrary.profiles[structure['parentId']] = { - groups:[] - } - } + let isParentTop = false - this.componentLibrary.profiles[structure['parentId']].groups.push({ - id: short.generate(), - groupId: null, - position: this.componentLibrary.profiles[structure['parentId']].groups.length, - structure: structure, - label: label - }) + if (pt !== false) { + let baseURI = pt.propertyURI + if (!pt.userValue[baseURI]) { + pt.userValue[baseURI] = [{}] + } + let userValue = JSON.parse(JSON.stringify(pt.userValue[baseURI][0])) - this.saveComponentLibrary() + // find the default values for this template if they exist + if (structure) { - }, + if (structure.parentId) { + if (structure.parentId.endsWith("Work") || structure.parentId.includes("Instance") || structure.parentId.endsWith("Hub") || structure.parentId.endsWith("Item")) { + isParentTop = true + } - /** Writes the component library to the local storage - * - */ - saveComponentLibrary(){ - window.localStorage.setItem('marva-componentLibrary',JSON.stringify(this.componentLibrary)) - }, + let defaultsProperty = false + if (this.rtLookup[structure.parentId]) { + for (let p of this.rtLookup[structure.parentId].propertyTemplates) { + // dose it have a default value? + if (p.valueConstraint.defaults && p.valueConstraint.defaults.length > 0) { + if (p.valueConstraint.valueTemplateRefs && p.valueConstraint.valueTemplateRefs.length > 0) { + // they are linking to another template in this template, so if we ant to populate the imformation we would need to know what predicate to use :((((( + if (this.rtLookup[p.valueConstraint.valueTemplateRefs[0]] && this.rtLookup[p.valueConstraint.valueTemplateRefs[0]].propertyTemplates && this.rtLookup[p.valueConstraint.valueTemplateRefs[0]].propertyTemplates.length == 1) { + let defaultPropertyToUse = this.rtLookup[p.valueConstraint.valueTemplateRefs[0]].propertyTemplates[0].propertyURI + // we know what to store in the value and we now know what property to use + userValue[p.propertyURI] = [] + for (let d of p.valueConstraint.defaults) { + let value = { + '@guid': short.generate(d.defaultLiteral, d.defaultURI) + } + // do we need to create a blank node for this value? + let useType = utilsRDF.suggestTypeProfile(p.propertyURI, pt) + if (useType === false) { + // did not find it in the profile, look to the network + useType = await utilsRDF.suggestTypeNetwork(p.propertyURI) + } + if (useType && useType != 'http://www.w3.org/2000/01/rdf-schema#Literal') { + value['@type'] = useType + value[defaultPropertyToUse] = [{ + '@guid': short.generate(d.defaultLiteral, d.defaultURI) + }] + if (d.defaultLiteral && d.defaultLiteral != '') { + value[defaultPropertyToUse][0][defaultPropertyToUse] = this.replaceDefaultPlaceHolder(d.defaultLiteral) + } + if (d.defaultURI && d.defaultURI != '') { + value['@id'] = d.defaultURI + } + } else { + if ((d.defaultLiteral && !d.defaultURI) || (d.defaultLiteral != '' && d.defaultURI == '')) { + value[defaultPropertyToUse] = this.replaceDefaultPlaceHolder(d.defaultLiteral) + } else { + value['@id'] = d.defaultURI + } + } + + userValue[p.propertyURI].push(value) + } + } else { + console.warn("Nested default template trying to insert values but there are multiple propertyTemplates so no clue which proerpty to look into for the default value: ", this.rtLookup[p.valueConstraint.valueTemplateRefs[0]]) + } + } else { + let blankNodeType = null + // we probably need to make a blank node, so find out what rdf type blank node is needed + if (p.valueConstraint && p.valueConstraint.valueDataType && p.valueConstraint.valueDataType.dataTypeURI) { + blankNodeType = p.valueConstraint.valueDataType.dataTypeURI + } + // overwrite it if there is anything there already + userValue[p.propertyURI] = [] + for (let d of p.valueConstraint.defaults) { + let value = { + '@guid': short.generate(d.defaultLiteral, d.defaultURI) + } + // if it just has a literal value and not a URI then don't create a blank node, just insert it using that literal property + if ((d.defaultLiteral && !d.defaultURI) || (d.defaultLiteral != '' && d.defaultURI == '')) { + value[p.propertyURI] = this.replaceDefaultPlaceHolder(d.defaultLiteral) + } else { + // it is a blank node + if (d.defaultLiteral) { + // console.log(newPt) + value['http://www.w3.org/2000/01/rdf-schema#label'] = [{ + '@guid': short.generate(), + 'http://www.w3.org/2000/01/rdf-schema#label': this.replaceDefaultPlaceHolder(d.defaultLiteral) + }] + } + if (d.defaultURI) { + value['@id'] = d.defaultURI + } + if (blankNodeType) { + value['@type'] = blankNodeType + } + } + // if we're not working at the top level, just add the default values + if (!pp) { + if (!isParentTop) { + userValue[p.propertyURI].push(value) + //otherwise, make sure the propertyURI matches the baseURI + } else if (isParentTop && p.propertyURI == baseURI) { + userValue[p.propertyURI].push(value) + } + } else { + let existing = utilsProfile.returnValueFromPropertyPath(pt, pp) // don't overwrite existing data + if (!existing && ["http://id.loc.gov/ontologies/bibframe/relationship", "http://id.loc.gov/ontologies/bibframe/status"].includes(p.propertyURI)) { + this.setValueSimple(componentGuid, null, pp, d.defaultURI, d.defaultLiteral) + } + } + } + } - /** Changes the group property in the storged component library data - * - */ - changeGroupComponentLibrary(id,groupId){ + } + } - for (let key in this.componentLibrary.profiles){ - for (let group of this.componentLibrary.profiles[key].groups){ - if (group.id == id){ - group.groupId = groupId - this.saveComponentLibrary() - return true - } - } + } + } else { + console.warn("No structure.parentId found") + } + } else { + alert("Error: no structure found") + } - } + if (!isParentTop) { + const oldValues = Object.keys(pt.userValue[baseURI][0]) + const newValues = Object.keys(JSON.parse(JSON.stringify(userValue))) - }, + // don't overwrite existing values + let matches = newValues.filter((nV) => !oldValues.includes(nV)) - /** Changes the group property in the storged component library data - * - */ - addFromComponentLibrary(id){ - let defaultLibrary = null - if (usePreferenceStore().returnValue('--b-edit-main-splitpane-properties-show-defaults')){ - defaultLibrary = defaultComponents.DefaultComponentLibrary.profiles - this.componentLibrary.profiles = Object.assign({}, this.componentLibrary.profiles, defaultLibrary) - } - for (let key in this.componentLibrary.profiles){ - for (let group of this.componentLibrary.profiles[key].groups){ - if (group.id == id){ - - // we are adding a sigle one here so groups are individual (group of 1) in this case - console.log("Adding thisone",group) - let component = JSON.parse(JSON.stringify(group.structure)) - - // For item's the parent ID won't match anything in the RTs because we've stripped it down - if (component.parentId.includes(":Item")){ - for (let rt in this.activeProfile.rt){ - if (rt.includes(component.parentId)){ - component.parentId = rt - break + Object.filter = (obj, predicate) => + Object.keys(obj) + .filter(key => predicate(key)) + .reduce((res, key) => Object.assign(res, { [key]: obj[key] }), {}); + + let filtered = Object.filter(JSON.parse(JSON.stringify(userValue)), key => matches.includes(key)) + + Object.assign(pt.userValue[baseURI][0], JSON.parse(JSON.stringify(filtered))) + } else { + //We're not in a nested component, so we can just set the userValue + // But don't overwrite an existing value + for (let key in pt.userValue) { + if (!key.startsWith('@') && typeof pt.userValue[key][0] == 'object' && Object.keys(pt.userValue[key][0]).length == 0) { + pt.userValue = JSON.parse(JSON.stringify(userValue)) + } } } + // they changed something + this.dataChanged() + + } else { + console.error('insertDefaultValuesComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) + } + }, + + + /** + * Duplicate / create new component + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {boolean} createEmpty - if true make the component have no pre-populated data, if false "duplicate" the data from the source component + * @return {void} + */ + duplicateComponent: async function (componentGuid, structure) { + let createEmpty = true + + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + + if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/adminMetadata') { + alert("Cannot create Admin Metadata") + return false } - // see if we can find its counter part in the acutal profile - if (this.activeProfile.rt[component.parentId]){ + //Ensure that the component is going to the right place by checking the structure.parentID + // the parentId of different kinds of titles don't include `work` or `instance`, so check the RT in the profile + let rt = utilsProfile.getRtTypeFromGuid(this.activeProfile, componentGuid) + let actionTarget = null + if (structure.parentId.includes("Instance") || rt.includes("Instance")) { + actionTarget = "Instance" + } else if (structure.parentId.includes("Work") || rt.includes("Work")) { + actionTarget = "Work" + } - // see if we can find the component - let ptObjFound = false + if (pt !== false) { + let profile + let propertyPosition + for (let r of this.activeProfile.rtOrder) { + propertyPosition = this.activeProfile.rt[r].ptOrder.indexOf(pt.id) - // if it is an admin metadata do something special - if (component.propertyURI == "http://id.loc.gov/ontologies/bibframe/adminMetadata"){ - for (let pt in this.activeProfile.rt[component.parentId].pt){ - if (this.activeProfile.rt[component.parentId].pt[pt].propertyURI == component.propertyURI && this.activeProfile.rt[component.parentId].pt[pt].adminMetadataType && this.activeProfile.rt[component.parentId].pt[pt].adminMetadataType == 'primary' ){ - ptObjFound = this.activeProfile.rt[component.parentId].pt[pt] - } + if (propertyPosition != -1 && (r.includes(rt) || actionTarget == null)) { + + profile = r + break + } } - // we are going to perform a quick replace here, saving the local identifier and local 040 note from the - let localId=null - let local040=null - if (ptObjFound && - ptObjFound.userValue && - ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && - ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && - ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"]){ - for (let lId of ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"] ){ - if (lId['@type'] == "http://id.loc.gov/ontologies/bibframe/Local"){ - localId = JSON.parse(JSON.stringify(lId)) - break - } - } + let key = pt.propertyURI.replace('http://', '').replace('https://', '').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "_") + '__' + ((pt.propertyLabel) ? pt.propertyLabel.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "").replace(/\s+/g, '_').toLowerCase() : "plabel") + let newPropertyId = key + '_' + (+ new Date()) + + + let newPt = JSON.parse(JSON.stringify(pt)) + + // cleanout the newPt + newPt.xmlSource = "" + newPt.dataLoaded = false + newPt.hasData = false + delete newPt.deleted + delete newPt.hideSubject + delete newPt.hideClassNum + delete newPt.hide + + newPt.id = newPropertyId + newPt['@guid'] = short.generate() + + + // console.log("Lookign at this PT", pt) + // console.log(this.activeProfile) + // console.log(propertyPosition) + // console.log(key,newPropertyId) + if (createEmpty) { + + + // store.state.activeUndoLog.push(`Added another property ${exportXML.namespaceUri(activeProfile.rt[profile].pt[id].propertyURI)}`) + + // console.log(activeProfile.rt[profile].pt[newPropertyId]) + // console.log(profile,newPropertyId) + newPt.userValue = { + '@guid': short.generate(), + '@root': newPt.propertyURI + } - if (ptObjFound && - ptObjFound.userValue && - ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && - ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && - ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"]){ - for (let n of ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"] ){ - if (JSON.stringify(n).indexOf("040") > -1){ - local040 = JSON.parse(JSON.stringify(n)) - break - } - } + if (newPt.activeType) { + newPt.userValue[newPt.propertyURI] = [ + { + '@type': newPt.activeType + } + ] } - console.log("localId",localId) - console.log("local040",local040) - // okay now do the same on the component we are about to use, but replace the two values with the ones we just extracted - if (component && - component.userValue && - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"]){ - let to_replace_with = [] - if (localId){ to_replace_with.push(JSON.parse(JSON.stringify(localId)))} - for (let lId of component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"] ){ - if (lId['@type'] == "http://id.loc.gov/ontologies/bibframe/Local"){ - // this is the localid from the saved component, we don't want it so don't do anything - }else{ - // this isn't one, dunno what it is? but add it to the new one - to_replace_with.push(lId) - } + + + // we also want to add any default values in if it is just a empty new property and not duping + let idPropertyId = newPt.propertyURI + let baseURI = newPt.propertyURI + // let defaults = null + let defaultsProperty + + let useProfile = profile + // if the profile is a multiple, like lc:RT:bf2:Monograph:Item-0 split off the -0 for it to find it in the RT lookup + if (!this.rtLookup[useProfile]) { + if (useProfile.includes('-')) { + useProfile = useProfile.split('-')[0] + } + } + // first check the top level + if (this.rtLookup[useProfile]) { + defaultsProperty = this.rtLookup[useProfile].propertyTemplates.filter((x) => { return (x.propertyURI === idPropertyId) ? true : false }) + if (defaultsProperty.length > 0) { + defaultsProperty = defaultsProperty[0] + + } } + } else { + // doesn't support duplicating components yet + } + - // replace it with what we have, if it did not find the thing then it will be [] and blank in the new data otherwise it will be replaced - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"] = to_replace_with + this.activeProfile.rt[profile].pt[newPropertyId] = JSON.parse(JSON.stringify(newPt)) + this.activeProfile.rt[profile].ptOrder.splice(propertyPosition + 1, 0, newPropertyId); + // reset this, otherwise it can cause unwanted behavior in the XML + this.activeProfile.rt[profile].pt[newPropertyId].deepHierarchy = false + if (structure) { + this.insertDefaultValuesComponent(newPt['@guid'], structure) } + // they changed something + this.dataChanged() - if (component && - component.userValue && - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"]){ - let to_replace_with = [] - if (local040){ to_replace_with.push(JSON.parse(JSON.stringify(local040)))} + // send back the component guid incase the UI needs to do something with it + return newPt['@guid'] - for (let n of component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"] ){ - if (JSON.stringify(n).indexOf("040") > -1){ - // this is the local040 from the saved component, we don't want it so don't do anything - }else{ - // this isn't one, dunno what it is? but add it to the new one - to_replace_with.push(n) - } - } - // replace it with what we have, if it did not find the thing then it will be [] and blank in the new data otherwise it will be replaced - component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"] = to_replace_with + } else { + console.error('duplicateComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) - } + } + }, + + /** + * Duplicate / create new component with a given userValue + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {object} structure - structure of the component being copied + * @param {string} profileName - name of the target profile + * @param {string} predecessor - id of componenet comes before in the order + * @return {array} the id and guid of the newPropertyId + */ + duplicateComponentGetId: async function (componentGuid, structure, profileName, predecessor) { + let createEmpty = true + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) - // we are going to zero out the userValue of the found AdminMetadata here so the process below replaces it with the new one and not add it as another - ptObjFound.userValue = {'@root': "http://id.loc.gov/ontologies/bibframe/adminMetadata"} + if (pt !== false) { + let profile + let propertyPosition + let key = pt.propertyURI.replace('http://', '').replace('https://', '').replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "_") + '__' + ((pt.propertyLabel) ? pt.propertyLabel.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "").replace(/\s+/g, '_').toLowerCase() : "plabel") + + let lastPosition = 0 + + for (let r of this.activeProfile.rtOrder) { + let tempPosition = JSON.parse(JSON.stringify(this.activeProfile.rt[r].ptOrder)).indexOf(predecessor) + if (tempPosition > 0) { + propertyPosition = tempPosition + } + + //find the last position in the order of related components so we can insert + // the new components at the end of that list + if (r == profileName) { // does this have any sideffects?! + for (let idx in this.activeProfile.rt[r].ptOrder) { + let item = this.activeProfile.rt[r].ptOrder[idx] + //TODO: fix order when there's a deleted element? Can't reproduce now + if (item.includes(key)) { + lastPosition = idx + } + } + profile = profileName + } - }else{ - // loop till we find the first one - for (let pt in this.activeProfile.rt[component.parentId].pt){ - if (this.activeProfile.rt[component.parentId].pt[pt].id == component.id){ - ptObjFound = this.activeProfile.rt[component.parentId].pt[pt] - } } + let newPropertyId = key + '_' + (+ new Date()) + + let newPt = JSON.parse(JSON.stringify(pt)) + newPt.id = newPropertyId + newPt['@guid'] = short.generate() - } - if (ptObjFound != false){ - console.log("Found orignal here:",ptObjFound) - if (this.compareComponentStructure(ptObjFound, component)){ //ptObjFound.hashCode == component.hashCode + // console.log("Lookign at this PT", pt) + // console.log(this.activeProfile) + // console.log(propertyPosition) + // console.log(key,newPropertyId) + if (createEmpty) { + newPt.userValue = { + '@guid': short.generate(), + '@root': newPt.propertyURI + } - // if the component we found in the system already has data in it then we are going to add a new component - // if it doesn't then just overwrite it completely with the one from the library + if (newPt.activeType) { + newPt.userValue[newPt.propertyURI] = [ + { + '@type': newPt.activeType + } + ] + } - // regardless we need to set the id - component['@guid'] = short.generate() + // we also want to add any default values in if it is just a empty new property and not duping + let idPropertyId = newPt.propertyURI + let baseURI = newPt.propertyURI + // let defaults = null + let defaultsProperty - // we also need to create new @guid valuse inside the userValue in case they add multiple versions of the same - component = this.componentLibraryUpdateUserValueGuid(component) + // If it's deleted, flip it + if (newPt.deleted) { + newPt.deleted = false + } - if (Object.keys(ptObjFound.userValue).length <= 1 || ptObjFound.id.includes("id_loc_gov_ontologies_bibframe_adminmetadata")){ - // if this is 1 or 0 then the userdata is empty, with just a @root property - // there is no user data added yet - // we can just overwrite whats there with our component - // we don't need to adjust the order, its 1-for-1 - // find it again and overwrite + let useProfile = profile + // if the profile is a multiple, like lc:RT:bf2:Monograph:Item-0 split off the -0 for it to find it in the RT lookup + if (!this.rtLookup[useProfile]) { + if (useProfile.includes('-')) { + useProfile = useProfile.split('-')[0] + } + } + // first check the top level + if (this.rtLookup[useProfile]) { + defaultsProperty = this.rtLookup[useProfile].propertyTemplates.filter((x) => { return (x.propertyURI === idPropertyId) ? true : false }) + if (defaultsProperty.length > 0) { + defaultsProperty = defaultsProperty[0] - // Admin metadata should overwrite the existing values, otherwise we create a new Admin field, which we shouldn't do - if (!ptObjFound.id.includes("id_loc_gov_ontologies_bibframe_adminmetadata")){ - for (let pt in this.activeProfile.rt[component.parentId].pt){ - if (this.activeProfile.rt[component.parentId].pt[pt].id == component.id){ - this.activeProfile.rt[component.parentId].pt[pt] = JSON.parse(JSON.stringify(component)) - this.dataChanged() - return [component.parentId,pt] } - } - } else { - // Need an exception for Admin to ensure we're not making changes to a different admin field - for (let pt in this.activeProfile.rt[component.parentId].pt){ - if (this.activeProfile.rt[component.parentId].pt[pt].id == ptObjFound.id){ - component.id = ptObjFound.id - this.activeProfile.rt[component.parentId].pt[pt] = JSON.parse(JSON.stringify(component)) - this.dataChanged() - return [component.parentId,pt] - } - } - } - - - }else{ - // we can't replace the one that is there, already has data, so construct a new place for it - - // first find out how many of these components there are - let total_components = 0 - for (let pt in this.activeProfile.rt[component.parentId].pt){ - if (this.activeProfile.rt[component.parentId].pt[pt].id.startsWith(component.id)){ - total_components++ - } - } - let newId = component.id + "_" + (total_components+1) - let oldId = JSON.parse(JSON.stringify(component.id)) - - // rename it - component.id = newId - // add it to the pt - this.activeProfile.rt[component.parentId].pt[newId] = JSON.parse(JSON.stringify(component)) - // add it to the order - // find the position of the last one - let insertAt = 0 - for (const [i, p] of this.activeProfile.rt[component.parentId].ptOrder.entries()){ - if (p.startsWith(oldId)){ - insertAt = i - } - } - this.activeProfile.rt[component.parentId].ptOrder.splice(insertAt+1, 0, newId); - this.dataChanged() - return [component.parentId,newId] - } + } + } else { + // doesn't support duplicating components yet + } + + this.activeProfile.rt[profile].pt[newPropertyId] = JSON.parse(JSON.stringify(newPt)) + // For moving titles between work/instance, we want to use the last postion, otherwise + // should be after the predecessor + if (predecessor == 'last') { + this.activeProfile.rt[profile].ptOrder.splice(Number(lastPosition) + 1, 0, newPropertyId) + } else { + this.activeProfile.rt[profile].ptOrder.splice(Number(propertyPosition) + 1, 0, newPropertyId) + } + + if (structure) { + this.insertDefaultValuesComponent(newPt['@guid'], structure) + } + // they changed something + this.dataChanged() - }else{ + return [newPropertyId, newPt['@guid']] - alert("ERROR: There seems to be a mismatch between the component you are trying to add and the components in the profile. Please delete this component from your library and recreate it") + } else { + console.error('duplicateComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) + return false + } + }, + /** + * Delete existing component + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @return {void} + */ + deleteComponent: async function (componentGuid) { + // locate the correct pt to work on in the activeProfile + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + + if (pt !== false) { + if (pt.propertyURI == 'http://id.loc.gov/ontologies/bibframe/adminMetadata') { + alert("Cannot delete Admin Metadata") + return false } + // the checklabel will be the URI and the label of the component, beceause there are some components that use the same property URI + let checkLabel = pt.propertyLabel + pt.propertyURI - }else{ - alert("ERRROR: Could not find the orginal profile template this component was built from.",component.id) - } + //adjust for items + if (pt.parentId.includes(":Item")) { + for (let rt in this.activeProfile.rt) { + if (rt.includes(pt.parentId)) { + pt.parentId = rt + break + } + } + } + // first see how many these properties exist in the resource + let propertyCount = 0 + for (let k in this.activeProfile.rt[pt.parentId].pt) { + if (this.activeProfile.rt[pt.parentId].pt[k].propertyLabel + this.activeProfile.rt[pt.parentId].pt[k].propertyURI == checkLabel && !this.activeProfile.rt[pt.parentId].pt[k].deleted) { + propertyCount++ + } + } - }else{ - alert("ERRROR: Trying to add a component but could not find the profile:", component.parentId) - } + // if the propertyCount is 1 then we are about to delete the only property + // so instead just blank out the user value so it still exists if they need to add a value - } - } + if (propertyCount > 1) { + this.activeProfile.rt[pt.parentId].pt[pt.id].deleted = true + } else { + for (let key in this.activeProfile.rt[pt.parentId].pt[pt.id].userValue) { + if (!key.startsWith('@')) { + delete this.activeProfile.rt[pt.parentId].pt[pt.id].userValue[key] + + // Cleanup for components that are uneditable + if (Object.keys(this.activeProfile.rt[pt.parentId].pt[pt.id]).includes("xmlSource")) { + delete this.activeProfile.rt[pt.parentId].pt[pt.id].xmlSource + this.activeProfile.rt[pt.parentId].pt[pt.id].hasData = false + this.activeProfile.rt[pt.parentId].pt[pt.id].deepHierarchy = false + this.activeProfile.rt[pt.parentId].pt[pt.id].dataLoaded = false + this.activeProfile.rt[pt.parentId].pt[pt.id].xmlHash = "" + } + } + } + } - } + this.dataChanged() - }, - /** - * Reduce the components down to their structure for comparison - * @param {Object} componentExisting - Component that exists in the Profile - * @param {Object} componentLibrary - Component from library - */ - compareComponentStructure(componentExisting, componentLibrary){ - if (componentExisting.hasCode == componentLibrary.hashCode){ return true} - // strip out the non-structural stuff - let found = JSON.parse(JSON.stringify(componentExisting)) - delete found['@guid'] - delete found['hashCode'] - delete found['hashCodeId'] - delete found['userValue'] - delete found['canBeHidden'] - delete found['dataLoaded'] - delete found['deepHierarchy'] - delete found['hasData'] - delete found['remark'] - delete found['parent'] - delete found['xmlHash'] - delete found['xmlSource'] - delete found['userModified'] - delete found['activeType'] - delete found['missingProfile'] - delete found['valueConstraint']['editable'] - delete found['valueConstraint']['repeatable'] - delete found['valueConstraint']['valueDataType']['dataTypeLabel'] - delete found['valueConstraint']['valueDataType']['remark'] - delete found['valueConstraint']['defaults'] - delete found['refTemplateUserValue'] - delete found['refTemplateUserValueKeys'] - - let libraryComponent = JSON.parse(JSON.stringify(componentLibrary)) - delete libraryComponent['@guid'] - delete libraryComponent['hashCode'] - delete libraryComponent['hashCodeId'] - delete libraryComponent['userValue'] - delete libraryComponent['activeType'] - delete libraryComponent['canBeHidden'] - delete libraryComponent['dataLoaded'] - delete libraryComponent['deepHierarchy'] - delete libraryComponent['hasData'] - delete libraryComponent['remark'] - delete libraryComponent['parent'] - delete libraryComponent['userModified'] - delete libraryComponent['xmlHash'] - delete libraryComponent['xmlSource'] - delete libraryComponent['missingProfile'] - delete libraryComponent['valueConstraint']['editable'] - delete libraryComponent['valueConstraint']['repeatable'] - delete libraryComponent['valueConstraint']['valueDataType']['dataTypeLabel'] - delete libraryComponent['valueConstraint']['valueDataType']['remark'] - delete libraryComponent['valueConstraint']['defaults'] - delete libraryComponent['refTemplateUserValue'] - delete libraryComponent['refTemplateUserValueKeys'] - - if (found['valueConstraint']['valueDataType'] && Object.keys(found['valueConstraint']['valueDataType'].length > 0)){ - for (let key of Object.keys(found['valueConstraint']['valueDataType'])){ - if (found['valueConstraint']['valueDataType'][key] == ""){ - delete found['valueConstraint']['valueDataType'][key] - } - } - } + } else { + console.error('deleteComponent: Cannot locate the component by guid', componentGuid, this.activeProfile) + console.log(JSON.stringify(this.activeProfile)) + } - if (libraryComponent['valueConstraint']['valueDataType'] && Object.keys(libraryComponent['valueConstraint']['valueDataType'].length > 0)){ - for (let key of Object.keys(libraryComponent['valueConstraint']['valueDataType'])){ - if (libraryComponent['valueConstraint']['valueDataType'][key] == ""){ - delete libraryComponent['valueConstraint']['valueDataType'][key] - } - } - } - - - // Some adjustments for Items - if (found['parentId'].includes("Item-")){ - found['parentId'] = found['parentId'].split('-')[0] - } - if (libraryComponent['parentId'].includes("Item-")){ - libraryComponent['parentId'] = libraryComponent['parentId'].split('-')[0] - } - - // Get everything into a reliable order - found.valueConstraint = Object.keys(found.valueConstraint).sort().reduce( - (obj, key) => { - obj[key] = found.valueConstraint[key]; - return obj; - }, - {} - ); - libraryComponent.valueConstraint = Object.keys(libraryComponent.valueConstraint).sort().reduce( - (obj, key) => { - obj[key] = libraryComponent.valueConstraint[key]; - return obj; - }, - {} - ); - let orderedFound = Object.keys(found).sort().reduce( - (obj, key) => { - obj[key] = found[key]; - return obj; - }, - {} - ); - let orderedLibrary = Object.keys(libraryComponent).sort().reduce( - (obj, key) => { - obj[key] = libraryComponent[key]; - return obj; - }, - {} - ); + }, - // update uris to be more generic. - let ordereString = JSON.stringify(orderedFound) - let libraryString = JSON.stringify(orderedLibrary) + /** + * When they change something run this function to update things like autosave ect + * + * @return {void} + */ + dataChanged: function () { + this.activeProfileSaved = false + + // this is a debounce so it doesn't run on every key stroke + window.clearTimeout(dataChangedTimeout) + dataChangedTimeout = window.setTimeout(() => { + this.setMostCommonNonLatinScript() + // also store it in the active profile + this.activeProfile.mostCommonNonLatinScript = this.mostCommonNonLatinScript + this.activeProfile.nonLatinScriptAgents = this.nonLatinScriptAgents + // console.log("this.activeProfilethis.activeProfilethis.activeProfile",this.activeProfile) + // this will trigger the preview rebuild + this.dataChangedTimestamp = Date.now() + // console.log("Data changed, this.activeProfile", this.activeProfile) + + // save the current record for undo + this.saveState() + + // if they have auto save on then save it also + if (usePreferenceStore().returnValue('--b-general-auto-save')) { + this.saveRecord() + } - ordereString = ordereString.replaceAll("//id.loc.gov", "//example.com") - libraryString = libraryString.replaceAll("//id.loc.gov", "//example.com") + }, 500) + }, - ordereString = ordereString.replaceAll("//preprod.id.loc.gov", "//example.com") - libraryString = libraryString.replaceAll("//preprod.id.loc.gov", "//example.com") + /** + * A helper that can be run before loading a new record to do any maintenance needed + * + * @return {void} + */ + prepareForNewRecord: function () { - orderedFound = JSON.parse(ordereString) - orderedLibrary = JSON.parse(libraryString) + this.activeProfile = {} + this.linkedData = {} - // console.info("existing: ", JSON.stringify(orderedFound)) - // console.info("library: ", JSON.stringify(orderedLibrary)) + }, - let orderedFoundHashCode = hashCode(JSON.stringify(orderedFound)) - let orderedLibraryHashCode = hashCode(JSON.stringify(orderedLibrary)) - // if the existing hashMatches the stripped down library hash - if (componentExisting.hashCode == orderedLibraryHashCode){ - return true - } else if (orderedFoundHashCode == orderedLibraryHashCode){ // if both stripped down components match - return true - } else { - return false - } + /** + * Build a new instance + * + * @param secondary {bool} whether this should an instance or a secondary instance + * @param lccn {string} the lccn for the record + * @return {void} + */ + createInstance: function (secondary = false, lccn = null) { + // find the RT for the instance of this profile orginally + // get the work rt + + let instanceName + let instanceRt + let workUri + + for (let rtId in this.activeProfile.rt) { + if (rtId.includes(":Work")) { + workUri = this.activeProfile.rt[rtId].URI + // now find the corosponding instance id + for (let allRt in this.profiles) { + if (this.profiles[allRt].rtOrder.indexOf(rtId) > -1) { + instanceName = this.profiles[allRt].rtOrder.filter(i => i.includes(":Instance"))[0] + instanceRt = JSON.parse(JSON.stringify(this.profiles[allRt].rt[instanceName])) + } + } + } + } + let instanceCount = 0; - }, + // gather info to add it + let instances = Object.keys(this.activeProfile.rt).filter(i => i.includes(":Instance")) + if (instances.length > 1) { + instanceCount = instances.length - 1 + } + // console.log('instances',instances) + // for (let i of instances){ + // if (i.includes('_')){ + // let nid = parseInt(i.split('_')[1]) + // if (nid > instances.length){ + // instanceCount = nid + // } + // } + // } + instanceCount++ + // console.log('instanceCount',instanceCount) + let newRtId = instanceName + '_' + instanceCount + instanceRt.isNew = true + this.activeProfile.rt[newRtId] = instanceRt + this.activeProfile.rtOrder.push(newRtId) - componentLibraryUpdateUserValueGuid(component){ - function traverse(target) { - for (const key in target) { - if (typeof target[key] === 'object') { - traverse(target[key]); - } else if (key === '@guid'){ - target[key] = short.generate() - } else { - // nothing - } - } - } - traverse(component.userValue); + // give it all new guids + for (let pt in this.activeProfile.rt[newRtId].pt) { + this.activeProfile.rt[newRtId].pt[pt]['@guid'] = short.generate() + // update the parentId + this.activeProfile.rt[newRtId].pt[pt].parentId = this.activeProfile.rt[newRtId].pt[pt].parentId.replace(instanceName, newRtId) + this.activeProfile.rt[newRtId].pt[pt].parent = this.activeProfile.rt[newRtId].pt[pt].parent.replace(instanceName, newRtId) - return component - }, + // If it's not mandatory, add it to ad hoc mode's emptyComponents list + this.addToAdHocMode(newRtId, pt) + } - /** Removes a component from the library - * - */ - delComponentLibrary(id){ - for (let key in this.componentLibrary.profiles){ - this.componentLibrary.profiles[key].groups = this.componentLibrary.profiles[key].groups.filter((c) => { return (c.id != id) }) - } - this.saveComponentLibrary() - }, - /** Renames a component's label in the library - * - */ - renameComponentLibrary(id,newLabel){ - for (let key in this.componentLibrary.profiles){ - for (let group of this.componentLibrary.profiles[key].groups){ - if (group.id == id){ - group.label = newLabel - this.saveComponentLibrary() - return true - } - } - } - }, + // setup the new instance's properies + // profile.rt[newRdId].URI = 'http://id.loc.gov/resources/instances/'+ translator.toUUID(translator.new()) - /** - * Set or unset the default nature of the component - */ - makeComponentDefault(id){ - for (let key in this.componentLibrary.profiles){ - for (let group of this.componentLibrary.profiles[key].groups){ - if (group.id == id){ - if (Object.keys(group).includes("useDefault")){ - group.useDefault = !group.useDefault + if (lccn != "") { + this.activeProfile.rt[newRtId].URI = "http://id.loc.gov/resources/instances/" + lccn } else { - group["useDefault"] = true + this.activeProfile.rt[newRtId].URI = utilsProfile.suggestURI(this.activeProfile, 'bf:Instance', workUri) } + this.activeProfile.rt[newRtId].instanceOf = workUri + + if (secondary && secondary != "item") { + this.activeProfile.rt[newRtId]['@type'] = 'http://id.loc.gov/ontologies/bflc/SecondaryInstance' + } + + this.activeProfile.rt[newRtId].deletable = true + + //Add to rtLookup, with a copy of an instance as the value + this.rtLookup[newRtId] = this.rtLookup[instanceName] + + this.dataChanged() + + }, + + /** + * Create a new item for the record + * @param instance {string} position of the rt for the instance that the item belongs to, when there is more than 1 instance + * @param lccn {string} the lccn for the record + * + * @return {void} + */ + createItem: async function (instance, lccn = null) { + // find the RT for the instance of this profile orginally + // get the work rt + + let itemName + let itemRt + let workUri + let instanceUri + + for (let rtId in this.activeProfile.rt) { + if (rtId.includes(":Work")) { + workUri = this.activeProfile.rt[rtId].URI + // now find the corresponding item id + for (let allRt in this.profiles) { + if (this.profiles[allRt].rtOrder.indexOf(rtId) > -1) { + if (this.profiles[allRt].rtOrder.filter(i => i.includes(":Item"))[0]) { + itemName = this.profiles[allRt].rtOrder.filter(i => i.includes(":Item"))[0] + itemRt = JSON.parse(JSON.stringify(this.profiles[allRt].rt[itemName])) + } + } + } + } + if (instance && rtId == instance) { + instanceUri = this.activeProfile.rt[rtId].URI + break + } else if (rtId.includes(":Instance")) { + instanceUri = this.activeProfile.rt[rtId].URI + } + } + let itemCount = 0; + // gather info to add it + let items = Object.keys(this.activeProfile.rt).filter(i => i.includes(":Item")) + if (items.length >= 1) { + itemCount = items.length + } + + + + // console.log('itemCount',itemCount) + let newRtId = itemName + '_' + itemCount + itemRt.isNew = true + this.activeProfile.rt[newRtId] = itemRt + this.activeProfile.rtOrder.push(newRtId) + + // give it all new guids + for (let pt in this.activeProfile.rt[newRtId].pt) { + this.activeProfile.rt[newRtId].pt[pt]['@guid'] = short.generate() + // update the parentId + this.activeProfile.rt[newRtId].pt[pt].parentId = this.activeProfile.rt[newRtId].pt[pt].parentId.replace(itemName, newRtId) + this.activeProfile.rt[newRtId].pt[pt].parent = this.activeProfile.rt[newRtId].pt[pt].parent.replace(itemName, newRtId) + + // If it's not mandatory, add it to ad hoc mode's emptyComponents list + this.addToAdHocMode(newRtId, pt) + } + + // setup the new instance's properies + // profile.rt[newRdId].URI = 'http://id.loc.gov/resources/instances/'+ translator.toUUID(translator.new()) + + //this.activeProfile.rt[newRtId].URI = utilsProfile.suggestURI(this.activeProfile,'bf:Item', instanceUri) + if (lccn && lccn != "") { + this.activeProfile.rt[newRtId].URI = "http://id.loc.gov/resources/items/" + lccn + } else { + this.activeProfile.rt[newRtId].URI = utilsProfile.suggestURI(this.activeProfile, 'bf:Item', instanceUri) + } + + this.activeProfile.rt[newRtId].itemOf = instanceUri + + //Add to rtLookup, with a copy of an instance as the value + this.rtLookup[newRtId] = this.rtLookup[itemName] + + this.dataChanged() + + }, + + /** + * Scans the entire active profile for paired literals where some have language tags and others don't. + * + * This method looks for instances where a property contains multiple literal values, + * and at least one value has a language tag (@language) while at least one other value + * does not. + * + * used for non-latin literals tool + * + * The method examines: + * 1. All nested literals inside blank nodes + * 2. Top-level literals in properties specified in the config's groupTopLeveLiterals + * + * @returns {Array} An array of objects containing: + * - ptObj: The property template object containing the literal + * - node: The node containing the untagged literal + * - propertyURI: The URI of the property + * - value: The literal value without a language tag + */ + returnPairedLiteralsWithNoLang() { + + let results = [] + function process(obj, func) { + if (obj && obj.userValue) { + obj = obj.userValue + } + if (Array.isArray(obj)) { + obj.forEach(function (child) { + process(child, func); + }); + } else if (typeof obj == 'object' && obj !== null) { + for (let k in obj) { + if (Array.isArray(obj[k])) { + if (!k.startsWith('@') && obj[k].length > 1) { + func(obj, k, obj[k]); + } + process(obj[k], func); + + } + } + } + + } + + + for (let rt of this.activeProfile.rtOrder) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + let ptObj = this.activeProfile.rt[rt].pt[pt] + process(ptObj, function (obj, key, value) { + // e.g. + // only array > 1 make it here + if (value.filter((v) => { return (v['@language']) }).length >= 1 && value.filter((v) => { return (v['@language']) }).length != value.length) { + // only arrays with @language in them make it here and only if they do nt all have it + + for (let n of value) { + if (!n['@language']) { + // some properties we don't care about + if (useConfigStore().excludeFromNonLatinLiteralCheck.indexOf(ptObj.propertyURI) !== -1) { + continue + } + // sepcial for paired check + if (['http://id.loc.gov/ontologies/bibframe/expressionOf'].indexOf(ptObj.propertyURI) !== -1) { + continue + } + results.push({ + ptObj: ptObj, + node: n, + propertyURI: key, + value: n[key] + }) + } + } + } + }); + } + } + return results + }, + + + /** + * + * + * @return {array} + */ + returnAllNonLatinLiterals: function (onlyAccessPoints = false) { + + function process(obj, func) { + + if (obj && obj.userValue) { + obj = obj.userValue + } + + if (Array.isArray(obj)) { + obj.forEach(function (child) { + process(child, func); + }); + } else if (typeof obj == 'object' && obj !== null) { + for (let k in obj) { + if (Array.isArray(obj[k])) { + process(obj[k], func); + } else { + if (!k.startsWith('@')) { + func(obj, k, obj[k]); + } + } + } + } + + } + + + + + let nonLatinNodes = [] + for (let rt of this.activeProfile.rtOrder) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + let ptObj = this.activeProfile.rt[rt].pt[pt] + + // we don't care about literals inside specific types of properties + // like agent or headings but we also use this function to grab the ones specificly for agents and headings + if (onlyAccessPoints) { + + if (useConfigStore().excludeFromNonLatinLiteralCheck.indexOf(ptObj.propertyURI) == -1) { + continue + } + + } else { + + if (ptObj && useConfigStore().excludeFromNonLatinLiteralCheck.indexOf(ptObj.propertyURI) > -1) { + continue + } + + } + + + process(ptObj, function (obj, key, value) { + // e.g. + + + + if (!latinRegex.test(value)) { + nonLatinNodes.push({ + ptObj: ptObj, + node: obj, + propertyURI: key, + value: value, + lang: obj['@language'], + }) + } + }); + + + + } + } + + // console.log("nonLatinNodes",nonLatinNodes) + return nonLatinNodes + }, + + returnAllNonLatinAgentOptions: function () { + + let nonLatin = this.returnAllNonLatinLiterals(true) + let nonLatinMap = {} + // console.log("nonLatin",nonLatin) + for (let nl of nonLatin) { + let ptFound = null + for (let rt of this.activeProfile.rtOrder) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + if (JSON.stringify(this.activeProfile.rt[rt].pt[pt].userValue).indexOf(nl.node['@guid']) > -1) { + ptFound = this.activeProfile.rt[rt].pt[pt] + } + if (ptFound) { break } + } + if (ptFound) { break } + } + + // grab the guid as the key + if (ptFound) { + if (!nonLatinMap[ptFound['@guid']]) { + nonLatinMap[ptFound['@guid']] = { + scripts: [], + '@guid': ptFound['@guid'], + 'propertyURI': ptFound.propertyURI, + nonLatin: this.returnLatinLabelForPt(ptFound) + } + } + if (nl && nl.node && nl.node['@language']) { + let script = nl.node['@language'].split("-")[1] + if (script) { + nonLatinMap[ptFound['@guid']].scripts.push(script) + } + } + + + } + // unique array + nonLatinMap[ptFound['@guid']].scripts = [...new Set(nonLatinMap[ptFound['@guid']].scripts)]; + + } + // console.log("nonLatinMap",nonLatinMap) + + return nonLatinMap + + + }, + + + returnLatinLabelForPt: function (pt) { + + let nonLatinFound = null + + function process(obj, func) { + if (nonLatinFound) { return } + if (obj && obj.userValue) { + obj = obj.userValue + } + if (Array.isArray(obj)) { + obj.forEach(function (child) { + process(child, func); + }); + } else if (typeof obj == 'object' && obj !== null) { + for (let k in obj) { + if (Array.isArray(obj[k])) { + process(obj[k], func); + } else { + if (!k.startsWith('@')) { + func(obj, k, obj[k]); + } + } + } + } + } + + process(pt, function (obj, key, value) { + if (key == 'http://www.w3.org/2000/01/rdf-schema#label') { + if (!obj['@language']) { + nonLatinFound = obj['http://www.w3.org/2000/01/rdf-schema#label'] + } + } + }); + + return nonLatinFound + }, + + /** + * Set lang of literal value + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} fieldGuid - the guid of the field + * @param {string} lang - the ISO rdf language value like 'en' to append to the literal 'xxxxx@en' + * @return {void} + */ + setBulkLang: function (componentGuid, fieldGuid, lang) { + + + let pt = utilsProfile.returnPt(this.activeProfile, componentGuid) + if (pt !== false) { + let blankNode = utilsProfile.returnGuidLocation(pt.userValue, fieldGuid) + if (blankNode) { + if (lang) { + blankNode['@language'] = lang + } else { + if (blankNode['@language']) { + delete blankNode['@language'] + } + } + + + } else { + console.warn("Cannot find blankNode", pt) + } + } else { + console.warn("Cannot find pt", componentGuid) + } + + + }, + + /** + * returns the validation status of the heading used + * + * @param {string} componentGuid - the guid of the component (the parent of all fields) + * @param {string} fieldGuid - the guid of the field + */ + returnValidationType: function (fieldGuid) { + + let fieldValue = null + + for (let rt of this.activeProfile.rtOrder) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + fieldValue = this.activeProfile.rt[rt].pt[pt] ? utilsProfile.returnGuidLocation(this.activeProfile.rt[rt].pt[pt].userValue, fieldGuid) : false + if (fieldValue) { break } + } + if (fieldValue) { break } + } + if (fieldValue) { + // if it has a component list then check all the components + if (fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'] && fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'].length > 0) { + + let allHasURI = true + let firstHasURI = false + for (let c of fieldValue['http://www.loc.gov/mads/rdf/v1#componentList']) { + if (!c['@id']) { allHasURI = false } + } + if (fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'][0] && fieldValue['http://www.loc.gov/mads/rdf/v1#componentList'][0]['@id']) { + firstHasURI = true + } + + if (allHasURI) { return ['done_all', 'Linked'] } + if (firstHasURI) { return ['warning', 'Partially Linked'] } + + return ['help', 'No Partial Link'] + } + + + + + + } + + return ['report', 'No Link'] + }, + + copySelected: async function (deleteSelected = false) { + let components = [] + let compontGuids = [] + let copyTargets = document.querySelectorAll('input[class=copy-selection]:checked') + + if (copyTargets.length == 0) { + console.warn("nothing to copy") + alert("Nothing selected to copy. Select the fields you would like to copy.") + + return false + } + + copyTargets.forEach((item) => compontGuids.push(item.id)) + + for (const guid of compontGuids) { + let component = utilsProfile.returnPt(this.activeProfile, guid) + let componentString = JSON.stringify(component) + components.push(componentString) + if (deleteSelected) { + this.deleteComponent(guid) + } + } + + //copy it + let value = components.join(" ;;; ") + const type = "text/plain" + const blob = new Blob([value], { type }) + const data = [new ClipboardItem({ [type]: blob })] + + await navigator.clipboard.write(data) + + //Add checkmark + let button = document.getElementById("copy-selected-button") + button.children[0].innerHTML = "check" + + //wait a few seconds and remove the check mark + setTimeout(function () { + button.children[0].innerHTML = "content_copy" + }, 2000) + + return true + }, + + //loop through the copied data and change all the "@guid"s + changeGuid: function (data) { + try { + for (let key of Object.keys(data)) { + if (key == "@guid") { + data[key] = short.generate() + } else if (Array.isArray(data[key])) { + this.changeGuid(data[key]) + } else if (typeof data[key] == "object") { + this.changeGuid(data[key]) + } + } + } catch (e) { + console.error("Error in changeGuid: ", e) + } + }, + + //parse the activeProfile and insert the copied data where appropriate + /** + * + * @param {Object} newComponent - The new component + * @param {String} sourceRt - RT it came from + * @param {String} incomingTargetRt - RT it's going to + */ + parseActiveInsert: async function (newComponent, sourceRt = null, incomingTargetRt = null) { + this.changeGuid(newComponent) + let profile = this.activeProfile + + // handle pasting into a profileRT that doesn't exist in the new profile + // This is for when the source is an additional Instance, that doesn't exist in + // in the title + let targetRt + if (!profile.rtOrder.includes(newComponent.parentId)) { + if (newComponent.parentId.includes("_")) { + targetRt = newComponent.parentId.split("_").at(0) + } else { + targetRt = newComponent.parentId + } + } else { + targetRt = newComponent.parentId + } + + if (incomingTargetRt) { + targetRt = incomingTargetRt + } + + for (let rt in profile["rt"]) { + let frozenPts = profile["rt"][rt]["pt"] + let order = profile["rt"][rt]["ptOrder"] + + for (let pt in frozenPts) { + let current = profile["rt"][rt]["pt"][pt] + if (rt == targetRt) { + let targetURI = newComponent.propertyURI + let targetLabel = newComponent.propertyLabel + + if (!current.deleted && current.propertyURI.trim() == targetURI.trim() && current.propertyLabel.trim() == targetLabel.trim()) { + let currentPos = order.indexOf(current.id) + let newPos = order.indexOf(newComponent.id) + + // if (Object.keys(current.userValue).length == 1){ + if (this.isEmptyComponent(current)) { + current.userValue = newComponent.userValue + break + } else { + const guid = current["@guid"] + let structure = this.returnStructureByComponentGuid(guid) + + let newPt + if ((sourceRt && sourceRt != targetRt) || (!sourceRt && !incomingTargetRt)) { + newPt = await this.duplicateComponentGetId(guid, structure, rt, "last") + } else { + if (newPos < 0) { + newPt = await this.duplicateComponentGetId(guid, structure, rt, current.id) + } else { + newPt = await this.duplicateComponentGetId(guid, structure, rt, newComponent.id) + } + } + + newPt = newPt[0] + + profile["rt"][rt]["pt"][newPt].userValue = newComponent.userValue + profile["rt"][rt]["pt"][newPt].userModified = true + break + } + } + } + } + } + }, + + pasteSelected: async function (sourceRt = null) { + + let data + const clipboardContents = await navigator.clipboard.read(); + + for (let item of clipboardContents) { + if (!item.types.includes("text/plain")) { + throw new Error("Clipboard does not contain text data."); + } + + let blob = await item.getType("text/plain") + const incomingValue = await blob.text() + + data = incomingValue.split(";;;") + } + for (let item of data) { + const dataJson = JSON.parse(item) + if (!sourceRt) { + sourceRt = this.returnRtByGUID(dataJson['@guid']) + } + this.parseActiveInsert(JSON.parse(JSON.stringify(dataJson)), sourceRt) + } + }, + + + //Check if the component's userValue is empty + isEmptyComponent: function (c) { + const component = c + const emptyArray = new Array("@root") + const userValue = JSON.parse(JSON.stringify(component["userValue"])) + + + // if there is only a @root + if (JSON.stringify(Object.keys(component.userValue)) == JSON.stringify(emptyArray)) { + return true + } else { + // if the children only have "@..." properties + for (let key in component.userValue) { + if (!key.startsWith("@")) { + let result = false + try { + if (userValue[key].length == 0) { return true } + // this makes sure that the propertiesPanel will have the correct symbol when the incoming data + // has an populate electronicLocator + if (component.propertyURI != "http://id.loc.gov/ontologies/bibframe/electronicLocator") { + result = Object.keys(userValue[key][0]).every((childKey) => childKey.startsWith("@")) + } else { + result = !Object.keys(userValue[key][0]).some((childKey) => childKey.startsWith("@id")) + } + } catch (err) { + console.debug("error: Checking if component is empty, ", err) + } + + return result + } + } + } + + return false + }, + + /** + * Will look at the literals being used on the record and pick the most common script found + * used to help pick the correct auth labels to include in the access points, + * will set this.mostCommonNonLatinScript + * @return {String} + */ + setMostCommonNonLatinScript() { + + let literals = this.returnAllNonLatinLiterals() + + // if (literals.length == 0){ + // literals = this.returnAllNonLatinLiterals(true) + // } + + let allScriptsFound = [] + for (let l of literals) { + if (l.node && l.node['@language'] && l.node['@language'].indexOf('-') > -1) { + let lang = l.node['@language'].split("-")[1].toLowerCase() + if (lang != 'latn') { + allScriptsFound.push(lang) + } + } + } + + if (allScriptsFound.length > 0) { + // get mode + let mostCommong = allScriptsFound.sort((a, b) => allScriptsFound.filter(v => v === a).length - allScriptsFound.filter(v => v === b).length).pop(); + // capitalize + mostCommong = String(mostCommong).charAt(0).toUpperCase() + String(mostCommong).slice(1) + this.mostCommonNonLatinScript = mostCommong + } else { + this.mostCommonNonLatinScript = null + } + + return this.mostCommonNonLatinScript + }, + + /** + * Return the URL to use for lookups for various types of lookups based on the active profile + * A way to get a URL to a lookup without having to hardcode it + * @param {string} property - the proerty short version name, like bf:language + * @return {String} + */ + returnProfileLookupUrl(property) { + + for (let rt of this.activeProfile.rtOrder) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + let purl = utilsParse.namespaceUri(this.activeProfile.rt[rt].pt[pt].propertyURI) + if (purl == property) { + if (this.activeProfile.rt[rt].pt[pt].valueConstraint && this.activeProfile.rt[rt].pt[pt].valueConstraint.useValuesFrom && this.activeProfile.rt[rt].pt[pt].valueConstraint.useValuesFrom.length > 0) { + return this.activeProfile.rt[rt].pt[pt].valueConstraint.useValuesFrom[0] + } + } + + + } + } + + + // we didn't find it see if it has instead a templateref value + for (let rt of this.activeProfile.rtOrder) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + let purl = utilsParse.namespaceUri(this.activeProfile.rt[rt].pt[pt].propertyURI) + if (purl == property) { + if (this.activeProfile.rt[rt].pt[pt].valueConstraint && this.activeProfile.rt[rt].pt[pt].valueConstraint.valueTemplateRefs && this.activeProfile.rt[rt].pt[pt].valueConstraint.valueTemplateRefs.length > 0) { + let lookForTemplate = this.activeProfile.rt[rt].pt[pt].valueConstraint.valueTemplateRefs[0] + for (let pName in this.profiles) { + if (this.profiles[pName].rtOrder.includes(lookForTemplate)) { + for (let p of this.profiles[pName].rt[lookForTemplate].ptOrder) { + let purl2 = utilsParse.namespaceUri(this.profiles[pName].rt[lookForTemplate].pt[p].propertyURI) + if (purl2 == property || purl2 == 'owl:sameAs') { + if (this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint && this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint.useValuesFrom && this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint.useValuesFrom.length > 0) { + return this.profiles[pName].rt[lookForTemplate].pt[p].valueConstraint.useValuesFrom[0] + } + } + } + } + } + } + } + } + } + + + return false + }, + + /** + * Builds and posts a Hub Stub + * + * @param {object} hubCreatorObj - obj with creator label, uri,marcKey + * @param {string} title - title string + * @param {string} langObj - {uri:"",label:""} + * @return {String} + */ + async buildPostHubStub(hubCreatorObj, title, variant, variantLanguage, langObj, catCode) { + + // console.log("hubCreatorObj",hubCreatorObj) + let xml = await utilsExport.createHubStubXML(hubCreatorObj, title, variant, variantLanguage, langObj, catCode) + + console.log(xml) + // let eid = 'e' + decimalTranslator.new() + let eid = 'e' + Date.now().toString() + // eid = eid.substring(0,8) + + // pass a fake activeprofile with id == Hub to trigger hub protocols + let pubResuts + try { + pubResuts = await utilsNetwork.publish(xml, eid, { id: 'Hub' }) + + } catch (error) { + console.log(error) + alert("There was an error creating your Hub. Please report this issue.") + } + + // // to simulate a post + // alert("Fake Hub Requesting") + // pubResuts = { + // "name": "bc331009-aa60-48b3-ba17-865fc7389f23", + // "publish": { + // "status": "published" + // }, + // "postLocation": "http://preprod-8299.id.loc.gov/resources/hubs/bf110051-532b-c50c-5d5c-baa4ea6d2044" + // } + + if (pubResuts && pubResuts.status) { + let hubId = pubResuts.postLocation ? pubResuts.postLocation.split('/').pop() : null + this.logEvent('PUBLISHED_HUB', { metadata: [hubId] }) + } + + return pubResuts + + + + }, + + /** + * Look for the Statement of Responsibility in the record to add to the 670 $b + * + */ + nacoStubReturnSoR() { + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Instance") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/responsibilityStatement") { + if (pt.userValue + && pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'][0]['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'] + ) { + let sor = pt.userValue['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'][0]['http://id.loc.gov/ontologies/bibframe/responsibilityStatement'] + + if (sor.endsWith(".")) { + sor = sor.slice(0, -1); + } + + return sor + } + } + } + } + } + return false + }, + + checkCip() { + /** + * CIP is signaled by + * + * Projected publication date (YYMM) is populated + * encoding level: prepublication + * 906 $e ecip + * + * in the instance. + * + * If either of the checks is true, flagging it as a CIP + */ + let isCip = false + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Instance") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + let userValue = pt.userValue + if (pt.propertyURI == "http://id.loc.gov/ontologies/bflc/projectedProvisionDate") { + try { + let value = userValue["http://id.loc.gov/ontologies/bflc/projectedProvisionDate"][0]["http://id.loc.gov/ontologies/bflc/projectedProvisionDate"] + if (value) { + isCip = true + } + } catch { } + } + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/adminMetadata" && pt.adminMetadataType == 'primary') { + try { + let encodingLevel = userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bflc/encodingLevel"][0] + let code = encodingLevel["http://id.loc.gov/ontologies/bibframe/code"][0]["http://id.loc.gov/ontologies/bibframe/code"] + if (code == 8 || code == '8') { + isCip = true + } + } catch { } + } + } + } + } + + return isCip + }, + + /** + * Retrieves the main title from the NACO stub work profile by traversing the resource template structure. + * Looks for a property with URI "http://id.loc.gov/ontologies/bibframe/title" and extracts its main title value. + * + * @returns {(string|false)} The main title string if found, false otherwise + * @access public + * @requires activeProfile - Profile must be loaded with valid RT structure + */ + nacoStubReturnMainTitle() { + let subtitle = '' + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Instance") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/title") { + if (pt.userValue + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] + ) { + // look for the one that is set as latin first, if we can find it + for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle']) { + if (aTitle['@language'] && aTitle['@language'].toLowerCase().indexOf('latn') > -1) { + subtitle = aTitle['http://id.loc.gov/ontologies/bibframe/subtitle'] + } + } + + // otherwise look for the first one that doesn't have a language tag + for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle']) { + if (!aTitle['@language']) { + subtitle = aTitle['http://id.loc.gov/ontologies/bibframe/subtitle'] + } + } + + // if we can't find one without a language tag, just return the first one + subtitle = pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] + } + } + } + } + } + + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Work") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/title") { + if (pt.userValue + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] + ) { + + // look for the one that is set as latin first, if we can find it + for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle']) { + if (aTitle['@language'] && aTitle['@language'].toLowerCase().indexOf('latn') > -1) { + let title = aTitle['http://id.loc.gov/ontologies/bibframe/mainTitle'] + + // remove the subtitle + if (title.includes(subtitle)) { + title = title.replace(subtitle, "") + title = title.split(" : ")[0] + } + return title + } + } + + // otherwise look for the first one that doesn't have a language tag + for (let aTitle of pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle']) { + if (!aTitle['@language']) { + let title = aTitle['http://id.loc.gov/ontologies/bibframe/mainTitle'] + + // remove the subtitle + if (title.includes(subtitle)) { + title = title.replace(subtitle, "") + title = title.split(" : ")[0] + } + return title + } + } + + // if we can't find one without a language tag, just return the first one + let title = pt.userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] + + // remove the subtitle + if (title.includes(subtitle)) { + title = title.replace(subtitle, "") + title = title.split(" : ")[0] + } + return title + } + } + } + } + } + return false + }, + + /** + * Retrieves the Library of Congress Control Number (LCCN) from the NACO stub profile. + * Searches through Instance resource templates for bibframe:identifiedBy property + * with type bibframe:Lccn. + * + * @returns {(string|false)} The LCCN string if found, false otherwise + * @access public + * @requires activeProfile - Profile must be loaded with valid RT structure + */ + nacoStubReturnLCCN() { + + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Instance") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/identifiedBy") { + if (pt.userValue + && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['@type'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/Lccn" + ) + return pt.userValue['http://id.loc.gov/ontologies/bibframe/identifiedBy'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'][0]['http://www.w3.org/1999/02/22-rdf-syntax-ns#value'].trim() + } + } + } + } + return false + }, + /** + * Retrieves the date from the profile by searching multiple date fields. + * Searches in order: + * 1. Instance provision activity simple date + * 2. Instance provision activity EDTF date + * 3. Work origin date + * + * @returns {(string|false)} The date string if found in any of the searched fields, false otherwise + * @access public + * @requires activeProfile - Profile must be loaded with valid RT structure + */ + nacoStubReturnDate() { + + let provisionActivityEDTFDate = null + let provisionActivitySimpleDate = null + let originDate = null + + for (let rt of this.activeProfile.rtOrder) { + // look in the instance first for provision activity + if (rt.indexOf(":Instance") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/provisionActivity") { + if (pt.userValue + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'] + ) { + provisionActivitySimpleDate = pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'][0]['http://id.loc.gov/ontologies/bflc/simpleDate'] + } + } + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/provisionActivity") { + if (pt.userValue + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'][0]['http://id.loc.gov/ontologies/bibframe/date'] + ) { + provisionActivityEDTFDate = pt.userValue['http://id.loc.gov/ontologies/bibframe/provisionActivity'][0]['http://id.loc.gov/ontologies/bibframe/date'][0]['http://id.loc.gov/ontologies/bibframe/date'] + } + } + + } + } + if (rt.indexOf(":Work") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/originDate") { + if (pt.userValue + && pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'][0] + && pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'][0]['http://id.loc.gov/ontologies/bibframe/originDate'] + ) { + originDate = pt.userValue['http://id.loc.gov/ontologies/bibframe/originDate'][0]['http://id.loc.gov/ontologies/bibframe/originDate'] + } + } + } + + } + } + + let useDate = false + if (provisionActivitySimpleDate) { + useDate = provisionActivitySimpleDate + } else if (provisionActivityEDTFDate) { + useDate = provisionActivityEDTFDate + } else if (originDate) { + useDate = originDate + } else { + useDate = false + } + if (useDate) { + useDate = useDate.replace(/\[/g, '').replace(/\]/g, '') // remove brackets if they exist + useDate = useDate.replace(/{/g, '').replace(/}/g, '') // remove brackets if they exist + + useDate = useDate.trim() + } + + + + return useDate + + }, + + + + + + /** + * Retrieves the Work URI from the NACO stub profile by searching through resource templates. + * Returns the first URI found in a resource template containing ":Work" in its name. + * + * @returns {(string|false)} The Work URI if found, false otherwise + * @access public + * @requires activeProfile - Profile must be loaded with valid RT structure + */ + nacoStubReturnWorkURI() { + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Work") > -1) { + if (this.activeProfile.rt[rt].URI) { + return this.activeProfile.rt[rt].URI + } + } + } + return false + }, + /** + * Retrieves the Work Instance from the NACO stub profile by searching through resource templates. + * Returns the first URI found in a resource template containing ":Work" in its name. + * + * @returns {(string|false)} The Work URI if found, false otherwise + * @access public + * @requires activeProfile - Profile must be loaded with valid RT structure + */ + nacoStubReturnInstanceURI() { + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Instance") > -1) { + if (this.activeProfile.rt[rt].URI) { + return this.activeProfile.rt[rt].URI + } + } + } + return false + }, + + nacoStubReturnPopulatedValue(guid) { + let pt = utilsProfile.returnPt(this.activeProfile, guid) + let URI = null + let marcKey = null + let marcKey4xx = null + + if (pt && + pt.userValue && + pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent'] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent'][0]) { + console.log("pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent']", pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent']) + let agent = pt.userValue['http://id.loc.gov/ontologies/bibframe/contribution'][0]['http://id.loc.gov/ontologies/bibframe/agent'][0] + if (agent && agent['@id']) { + URI = agent['@id'] + } + if (agent && agent['http://id.loc.gov/ontologies/bflc/marcKey'] && + agent['http://id.loc.gov/ontologies/bflc/marcKey'][0] && + agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] + ) { + marcKey = agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] + } + + if (agent && agent['http://id.loc.gov/ontologies/bflc/marcKey'] && + agent['http://id.loc.gov/ontologies/bflc/marcKey'][1] && + agent['http://id.loc.gov/ontologies/bflc/marcKey'][1]['http://id.loc.gov/ontologies/bflc/marcKey'] + ) { + marcKey4xx = agent['http://id.loc.gov/ontologies/bflc/marcKey'][1]['http://id.loc.gov/ontologies/bflc/marcKey'] + } + + + + + + } + + if (pt && + pt.userValue && + pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0]) { + + let agent = pt.userValue['http://id.loc.gov/ontologies/bibframe/subject'][0] + if (agent && agent['@id']) { + URI = agent['@id'] + } + if (agent && agent['http://id.loc.gov/ontologies/bflc/marcKey'] && + agent['http://id.loc.gov/ontologies/bflc/marcKey'][0] && + agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] + ) { + marcKey = agent['http://id.loc.gov/ontologies/bflc/marcKey'][0]['http://id.loc.gov/ontologies/bflc/marcKey'] + } + + } + + return { + URI: URI, + marcKey: marcKey, + marcKey4xx: marcKey4xx + } + + }, + + nacoStubReturn245(guid) { + let subfieldA = "" + let subfieldB = "" + let subfieldC = "" + + for (let rt of this.activeProfile.rtOrder) { + if (rt.indexOf(":Instance") > -1) { + for (let pt of this.activeProfile.rt[rt].ptOrder) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyLabel == "Title information") { + let userValue = pt.userValue + if ( + userValue['http://id.loc.gov/ontologies/bibframe/title'] && + userValue['http://id.loc.gov/ontologies/bibframe/title'][0] && + userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] + ) { + let titleComp = userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/mainTitle'] + subfieldA = titleComp[0]["http://id.loc.gov/ontologies/bibframe/mainTitle"] + } + + if ( + userValue['http://id.loc.gov/ontologies/bibframe/title'] && + userValue['http://id.loc.gov/ontologies/bibframe/title'][0] && + userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] + ) { + let titleComp = userValue['http://id.loc.gov/ontologies/bibframe/title'][0]['http://id.loc.gov/ontologies/bibframe/subtitle'] + subfieldB = titleComp[0]["http://id.loc.gov/ontologies/bibframe/subtitle"] + } + } + } + } + } + + subfieldC = this.nacoStubReturnSoR() + + return { + subA: subfieldA, + subB: subfieldB, + subC: subfieldC + } + + }, + + + + /** + * Builds and posts a NACO Stub + * + * @param {object} hubCreatorObj - obj with creator label, uri,marcKey + * @param {string} title - title string + * @param {string} langObj - {uri:"",label:""} + * @return {String} + */ + async buildNacoStub(oneXX, fourXX, mainTitle, workURI, mainTitleDate, mainTitleLccn, mainTitleNote, zero46, add667, extraMarcStatements, useAdvancedMode) { + console.log(oneXX, fourXX, mainTitle, workURI, zero46) + let lccn = await utilsNetwork.nacoLccn() + if (lccn) { + this.logEvent('NACO_LCCN_ISSUED', { metadata: [lccn] }) + } + let NARData = await utilsExport.createNacoStubXML(oneXX, fourXX, mainTitle, lccn, workURI, mainTitleDate, mainTitleLccn, mainTitleNote, zero46, add667, extraMarcStatements, useAdvancedMode) + NARData.lccn = lccn + return NARData + }, + + async postNacoStub(xml, lccn) { + + let pubResuts + + try { + pubResuts = await utilsNetwork.publishNar(xml) + } catch (error) { + console.log(error) + alert("There was an error creating your NAR. Please report this issue.") + } + + // pubResuts = {'postLocation': 'https://id.loc.gov/authorities/names/n83122656', status: 'published'} + console.log('pubResuts') + console.log(pubResuts) + + if (pubResuts && pubResuts.status === 'published') { + this.logEvent('PUBLISHED_NAR', { metadata: [lccn] }) + } else if (pubResuts && pubResuts.status === true) { + this.logEvent('PUBLISHED_NAR', { metadata: [lccn] }) + } + + return { + xml: xml, + pubResuts: pubResuts, + lccn: lccn + } + + }, + + + /** Add the DDC to marva + * + * @param {object} deweyInfo - The dewey information that will be inserted into Marva + * @param {string} guid - The GUID of the LCC component that will be used to create the new component + * @param {string} structure - the structure that will be used to create the new component + */ + addDdc: async function (deweyInfo, guid, structure) { + //Look to see if there is a DDC component + let activeProfile = this.activeProfile + let hasEmptyDDC = false + let ddcComponent = null + let lastClassifiction = null + let newDDC + + for (let pt in activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt) { + if (pt.includes("id_loc_gov_ontologies_bibframe_classification__classification_numbers")) { + const target = activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt[pt] + const userValue = target.userValue + const classification = userValue["http://id.loc.gov/ontologies/bibframe/classification"][0] + const type = classification["@type"] + if (!pt.deleted) { + lastClassifiction = pt + //type == "http://id.loc.gov/ontologies/bibframe/ClassificationDdc" && + if (!Object.keys(classification).includes("http://id.loc.gov/ontologies/bibframe/classificationPortion")) { + // if (type == "http://id.loc.gov/ontologies/bibframe/ClassificationDdc"){ + hasEmptyDDC = true + ddcComponent = classification + newDDC = [target.id] + } + } + } + } + + // if no empty ddc, create one + if (!hasEmptyDDC) { + newDDC = await this.duplicateComponentGetId(this.returnStructureByComponentGuid(guid)['@guid'], structure, "lc:RT:bf2:Monograph:Work", lastClassifiction) + ddcComponent = activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt[newDDC[0]] + } + + //add information to component + let userValue = null + try { + userValue = ddcComponent.userValue["http://id.loc.gov/ontologies/bibframe/classification"][0] + } catch { + userValue = ddcComponent + } + + let dewey = null + if (Object.keys(deweyInfo).includes("DDC")) { + dewey = deweyInfo.DDC + } else { + dewey = deweyInfo.dewey + } + const newGuid = short.generate() + userValue["@type"] = "http://id.loc.gov/ontologies/bibframe/ClassificationDdc" + userValue["http://id.loc.gov/ontologies/bibframe/classificationPortion"] = [{ "@guid": newGuid, "http://id.loc.gov/ontologies/bibframe/classificationPortion": String(dewey) }] + + //Add the defaults: + const newComponent = activeProfile.rt["lc:RT:bf2:Monograph:Work"].pt[newDDC[0]] + + // look up one level & use the appropriate structure + let parentStructure = this.returnStructureByComponentGuid(newComponent['@guid']) + if (parentStructure.valueConstraint && parentStructure.valueConstraint.valueTemplateRefs && parentStructure.valueConstraint.valueTemplateRefs.length > 0) { + for (let vRt of parentStructure.valueConstraint.valueTemplateRefs) { + if (this.rtLookup[vRt] && vRt == "lc:RT:bf2:DDC") { + for (let pt of this.rtLookup[vRt].propertyTemplates) { + if (pt.valueConstraint.defaults && pt.valueConstraint.defaults.length > 0) { + this.insertDefaultValuesComponent(newComponent['@guid'], pt) + } + } + } + } + } + }, + + /** + * Insert a subject heading from the Yoshino recommendation pipeline into the active profile. + * Finds or creates an empty subject component in the Work RT and populates it. + * + * @param {string} label - The subject heading label + * @param {string} source - The vocabulary source name (e.g. "Library of Congress Subject Headings") + * @param {array} components - Parsed component data from the RDF [{label, uri, type, marcKey}, ...] + * @param {string|null} uri - Top-level subject URI (e.g. from rdf:about on the subject element) + * @param {string|null} marcKey - Top-level marcKey for the whole heading + */ + yoshinoInsertSubject: async function (label, source, components, uri, marcKey) { + let activeProfile = this.activeProfile + let workRt = null + let emptySubjectPt = null + let lastSubjectPt = null + console.log("label, source, components, uri, marcKey", label, source, components, uri, marcKey) + // Find the Work RT + for (let rt of activeProfile.rtOrder) { + if (rt.indexOf(':Work') > -1) { + workRt = rt + break + } + } + if (!workRt) return + + // Find an empty subject component or the last subject component + for (let ptId of activeProfile.rt[workRt].ptOrder) { + let pt = activeProfile.rt[workRt].pt[ptId] + if (pt && pt.propertyURI === 'http://id.loc.gov/ontologies/bibframe/subject' && !pt.deleted) { + lastSubjectPt = pt + let uv = pt.userValue + if (!pt.hasData || !uv || + !uv['http://id.loc.gov/ontologies/bibframe/subject'] || + uv['http://id.loc.gov/ontologies/bibframe/subject'].length === 0 || + !uv['http://id.loc.gov/ontologies/bibframe/subject'][0]['@type']) { + emptySubjectPt = pt + } + } + } + + // If no empty subject component, duplicate the last one to create a new empty one + let targetPt = emptySubjectPt + if (!targetPt && lastSubjectPt) { + let newGuid = await this.duplicateComponent(lastSubjectPt['@guid'], this.returnStructureByGUID(lastSubjectPt['@guid'])) + if (newGuid) { + targetPt = this.returnStructureByGUID(newGuid) + } + } + + if (!targetPt) return + + // Build the userValue + let subjectValue = { + '@guid': translator.new(), + 'http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme': [{ + '@guid': translator.new(), + '@id': 'http://id.loc.gov/authorities/subjects' + }], + 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': [{ + '@guid': translator.new(), + 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': label + }], + 'http://www.w3.org/2000/01/rdf-schema#label': [{ + '@guid': translator.new(), + 'http://www.w3.org/2000/01/rdf-schema#label': label + }], + } + + // Set top-level @id if provided (the URI of the whole subject heading) + if (uri) { + subjectValue['@id'] = uri + } + + // Set top-level marcKey if provided + if (marcKey) { + subjectValue['http://id.loc.gov/ontologies/bflc/marcKey'] = [{ + '@guid': translator.new(), + 'http://id.loc.gov/ontologies/bflc/marcKey': marcKey + }] + } + + // Use rich component data if available for complex subjects + if (components && components.length > 1) { + // subjectValue['@type'] = 'madsrdf:ComplexSubject' + subjectValue['@type'] = 'bf:Topic' + subjectValue["http://www.w3.org/2000/01/rdf-schema#type"] = [{ + "@guid": short.generate(), + "@id": "http://www.loc.gov/mads/rdf/v1#ComplexSubject" + }] + subjectValue['http://www.loc.gov/mads/rdf/v1#componentList'] = components.map(c => { + let comp = { + '@guid': translator.new(), + '@type': c.type || 'http://www.loc.gov/mads/rdf/v1#Topic', + 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': [{ + '@guid': translator.new(), + 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': c.label + }] + } + if (c.uri) { + comp['@id'] = c.uri + } + if (c.marcKey) { + comp['http://id.loc.gov/ontologies/bflc/marcKey'] = [{ + '@guid': translator.new(), + 'http://id.loc.gov/ontologies/bflc/marcKey': c.marcKey + }] + } + return comp + }) + } else if (components && components.length === 1) { + // Solo subject with rich data - type from component + subjectValue['@type'] = components[0].type || 'http://www.loc.gov/mads/rdf/v1#Topic' + } else if (label.includes('--')) { + // Fallback: parse from label if no component data + // subjectValue['@type'] = 'madsrdf:ComplexSubject' + subjectValue['@type'] = 'bf:Topic' + subjectValue["http://www.w3.org/2000/01/rdf-schema#type"] = [{ + "@guid": short.generate(), + "@id": "http://www.loc.gov/mads/rdf/v1#ComplexSubject" + }] + let parts = label.split('--').map(s => s.trim()) + subjectValue['http://www.loc.gov/mads/rdf/v1#componentList'] = parts.map(part => ({ + '@guid': translator.new(), + '@type': 'http://www.loc.gov/mads/rdf/v1#Topic', + 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': [{ + '@guid': translator.new(), + 'http://www.loc.gov/mads/rdf/v1#authoritativeLabel': part + }] + })) + } else { + subjectValue['@type'] = 'http://www.loc.gov/mads/rdf/v1#Topic' + } + + // Add source + if (source && (source.toLowerCase().includes('lcsh') || source.toLowerCase().includes('library of congress subject'))) { + subjectValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ + '@guid': translator.new(), + '@type': 'http://id.loc.gov/ontologies/bibframe/Source', + '@id': 'http://id.loc.gov/vocabulary/subjectSchemes/lcsh', + 'http://www.w3.org/2000/01/rdf-schema#label': [{ + '@guid': translator.new(), + 'http://www.w3.org/2000/01/rdf-schema#label': 'Library of Congress subject headings' + }] + }] + } else if (source && source.toLowerCase().includes('children')) { + subjectValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ + '@guid': translator.new(), + '@type': 'http://id.loc.gov/ontologies/bibframe/Source', + '@id': 'http://id.loc.gov/vocabulary/subjectSchemes/cyac', + 'http://www.w3.org/2000/01/rdf-schema#label': [{ + '@guid': translator.new(), + 'http://www.w3.org/2000/01/rdf-schema#label': source + }] + }] + } else if (source && source.toLowerCase().includes('mesh')) { + subjectValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ + '@guid': translator.new(), + '@type': 'http://id.loc.gov/ontologies/bibframe/Source', + '@id': 'http://id.loc.gov/vocabulary/subjectSchemes/mesh', + 'http://www.w3.org/2000/01/rdf-schema#label': [{ + '@guid': translator.new(), + 'http://www.w3.org/2000/01/rdf-schema#label': source + }] + }] + } + + // Set the userValue + targetPt.userValue = { + '@guid': targetPt.userValue?.['@guid'] || translator.new(), + '@root': 'http://id.loc.gov/ontologies/bibframe/subject', + 'http://id.loc.gov/ontologies/bibframe/subject': [subjectValue] + } + + console.log('--- Yoshino: Insert Subject ---') + console.log('Label:', label) + console.log('Source:', source) + console.log('Top-level URI:', uri) + console.log('Top-level marcKey:', marcKey) + console.log('Components:', JSON.parse(JSON.stringify(components || []))) + console.log('Built userValue:', JSON.parse(JSON.stringify(targetPt.userValue))) + + targetPt.hasData = true + targetPt.userModified = true + targetPt.dataLoaded = false + + this.dataChanged() + }, + + yoshinoInsertClassification: async function (classification) { + let activeProfile = this.activeProfile + let workRt = null + let emptyPt = null + let lastPt = null + + for (let rt of activeProfile.rtOrder) { + if (rt.indexOf(':Work') > -1) { + workRt = rt + break + } + } + if (!workRt) return + + for (let ptId of activeProfile.rt[workRt].ptOrder) { + let pt = activeProfile.rt[workRt].pt[ptId] + if (pt && pt.propertyURI === 'http://id.loc.gov/ontologies/bibframe/classification' && !pt.deleted) { + lastPt = pt + let uv = pt.userValue + if (!pt.hasData || !uv || + !uv['http://id.loc.gov/ontologies/bibframe/classification'] || + uv['http://id.loc.gov/ontologies/bibframe/classification'].length === 0 || + !uv['http://id.loc.gov/ontologies/bibframe/classification'][0]['@type']) { + emptyPt = pt + } + } + } + + let targetPt = emptyPt + if (!targetPt && lastPt) { + let newGuid = await this.duplicateComponent(lastPt['@guid'], this.returnStructureByGUID(lastPt['@guid'])) + if (newGuid) { + targetPt = this.returnStructureByGUID(newGuid) + } + } + if (!targetPt) return + + let classValue = { + '@guid': translator.new(), + '@type': classification.type, + 'http://id.loc.gov/ontologies/bibframe/classificationPortion': [{ + '@guid': translator.new(), + 'http://id.loc.gov/ontologies/bibframe/classificationPortion': classification.portion + }] + } + + if (classification.sourceCode) { + classValue['http://id.loc.gov/ontologies/bibframe/source'] = [{ + '@guid': translator.new(), + '@type': 'http://id.loc.gov/ontologies/bibframe/Source', + 'http://id.loc.gov/ontologies/bibframe/code': [{ + '@guid': translator.new(), + 'http://id.loc.gov/ontologies/bibframe/code': classification.sourceCode + }] + }] + } + + if (classification.edition) { + classValue['http://id.loc.gov/ontologies/bibframe/edition'] = [{ + '@guid': translator.new(), + 'http://id.loc.gov/ontologies/bibframe/edition': classification.edition + }] + } + + targetPt.userValue = { + '@guid': targetPt.userValue?.['@guid'] || translator.new(), + '@root': 'http://id.loc.gov/ontologies/bibframe/classification', + 'http://id.loc.gov/ontologies/bibframe/classification': [classValue] + } + + targetPt.hasData = true + targetPt.userModified = true + targetPt.dataLoaded = false + + this.dataChanged() + }, + + /** Add a component to the library + * + * @param {string} guid - The GUID of the component + */ + addToComponentLibrary: async function (guid) { + let structure = JSON.parse(JSON.stringify(this.returnStructureByComponentGuid(guid))) + + // clean up component property values for storage + structure['@guid'] = null + // does the id end with a number, if so it is a duplicated component, or one of multiple, so remove that value + let lastIdPart = structure['id'].split("_").slice(-1)[0] + if (lastIdPart >= '0' && lastIdPart <= '9') { + // it is a number + let newId = structure['id'].split("_") + newId = newId.slice(0, -1) + newId = newId.join("_") + structure['id'] = newId + } + + let label = prompt("What to call this component?", structure.propertyLabel) + if (!label) { + return false + } + + if (structure['parentId'].includes(":Item")) { + let key = structure['parentId'] + if (key && key.includes(":Item")) { + if (key.includes("-") || key.includes("_")) { + let idx + idx = key.indexOf("_") + if (idx < 0) { + idx = key.indexOf("-") + } + key = key.slice(0, idx) + } + } + structure['parentId'] = key + } + + + if (!this.componentLibrary.profiles[structure['parentId']]) { + this.componentLibrary.profiles[structure['parentId']] = { + groups: [] + } + } + + this.componentLibrary.profiles[structure['parentId']].groups.push({ + id: short.generate(), + groupId: null, + position: this.componentLibrary.profiles[structure['parentId']].groups.length, + structure: structure, + label: label + }) this.saveComponentLibrary() - return true - } - } - } - }, - /** - * - * @param {string} profileName - name of the profile the element belongs to - * @param {string} element - the id of the element that will be added - */ - addToAdHocMode: function(profileName, element){ - let target = this.activeProfile.rt[profileName].pt[element] - let empty = this.isEmptyComponent(this.activeProfile.rt[profileName].pt[element]) - if (empty && target.mandatory != 'true'){ - if (Object.keys(this.emptyComponents).includes(profileName)){ - this.emptyComponents[profileName].push(element) - } else { - this.emptyComponents[profileName] = [element] - } - } else { - console.warn(element, " is mandatory or populated. Didn't hide it.") - } - }, + }, + + /** Writes the component library to the local storage + * + */ + saveComponentLibrary() { + window.localStorage.setItem('marva-componentLibrary', JSON.stringify(this.componentLibrary)) + }, + + /** Changes the group property in the storged component library data + * + */ + changeGroupComponentLibrary(id, groupId) { + + for (let key in this.componentLibrary.profiles) { + for (let group of this.componentLibrary.profiles[key].groups) { + if (group.id == id) { + group.groupId = groupId + this.saveComponentLibrary() + return true + } + } + + } + + }, + + /** Changes the group property in the storged component library data + * + */ + addFromComponentLibrary(id) { + let defaultLibrary = null + if (usePreferenceStore().returnValue('--b-edit-main-splitpane-properties-show-defaults')) { + defaultLibrary = defaultComponents.DefaultComponentLibrary.profiles + this.componentLibrary.profiles = Object.assign({}, this.componentLibrary.profiles, defaultLibrary) + } + for (let key in this.componentLibrary.profiles) { + for (let group of this.componentLibrary.profiles[key].groups) { + if (group.id == id) { + + // we are adding a sigle one here so groups are individual (group of 1) in this case + console.log("Adding thisone", group) + let component = JSON.parse(JSON.stringify(group.structure)) + + // For item's the parent ID won't match anything in the RTs because we've stripped it down + if (component.parentId.includes(":Item")) { + for (let rt in this.activeProfile.rt) { + if (rt.includes(component.parentId)) { + component.parentId = rt + break + } + } + } + + // see if we can find its counter part in the acutal profile + if (this.activeProfile.rt[component.parentId]) { + + // see if we can find the component + let ptObjFound = false + + // if it is an admin metadata do something special + if (component.propertyURI == "http://id.loc.gov/ontologies/bibframe/adminMetadata") { + for (let pt in this.activeProfile.rt[component.parentId].pt) { + if (this.activeProfile.rt[component.parentId].pt[pt].propertyURI == component.propertyURI && this.activeProfile.rt[component.parentId].pt[pt].adminMetadataType && this.activeProfile.rt[component.parentId].pt[pt].adminMetadataType == 'primary') { + ptObjFound = this.activeProfile.rt[component.parentId].pt[pt] + } + } + + // we are going to perform a quick replace here, saving the local identifier and local 040 note from the + let localId = null + let local040 = null + if (ptObjFound && + ptObjFound.userValue && + ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && + ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && + ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"]) { + for (let lId of ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"]) { + if (lId['@type'] == "http://id.loc.gov/ontologies/bibframe/Local") { + localId = JSON.parse(JSON.stringify(lId)) + break + } + } + } + + if (ptObjFound && + ptObjFound.userValue && + ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && + ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && + ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"]) { + for (let n of ptObjFound.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"]) { + if (JSON.stringify(n).indexOf("040") > -1) { + local040 = JSON.parse(JSON.stringify(n)) + break + } + } + } + console.log("localId", localId) + console.log("local040", local040) + + // okay now do the same on the component we are about to use, but replace the two values with the ones we just extracted + if (component && + component.userValue && + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"]) { + let to_replace_with = [] + if (localId) { to_replace_with.push(JSON.parse(JSON.stringify(localId))) } + + for (let lId of component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"]) { + if (lId['@type'] == "http://id.loc.gov/ontologies/bibframe/Local") { + // this is the localid from the saved component, we don't want it so don't do anything + } else { + // this isn't one, dunno what it is? but add it to the new one + to_replace_with.push(lId) + } + } + + // replace it with what we have, if it did not find the thing then it will be [] and blank in the new data otherwise it will be replaced + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/identifiedBy"] = to_replace_with + + } + + + if (component && + component.userValue && + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"] && + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0] && + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"]) { + let to_replace_with = [] + if (local040) { to_replace_with.push(JSON.parse(JSON.stringify(local040))) } + + for (let n of component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"]) { + if (JSON.stringify(n).indexOf("040") > -1) { + // this is the local040 from the saved component, we don't want it so don't do anything + } else { + // this isn't one, dunno what it is? but add it to the new one + to_replace_with.push(n) + } + } + // replace it with what we have, if it did not find the thing then it will be [] and blank in the new data otherwise it will be replaced + component.userValue["http://id.loc.gov/ontologies/bibframe/adminMetadata"][0]["http://id.loc.gov/ontologies/bibframe/note"] = to_replace_with + + } + + + // we are going to zero out the userValue of the found AdminMetadata here so the process below replaces it with the new one and not add it as another + ptObjFound.userValue = { '@root': "http://id.loc.gov/ontologies/bibframe/adminMetadata" } + + + } else { + // loop till we find the first one + for (let pt in this.activeProfile.rt[component.parentId].pt) { + if (this.activeProfile.rt[component.parentId].pt[pt].id == component.id) { + ptObjFound = this.activeProfile.rt[component.parentId].pt[pt] + } + } + + + } + + if (ptObjFound != false) { + console.log("Found orignal here:", ptObjFound) + if (this.compareComponentStructure(ptObjFound, component)) { //ptObjFound.hashCode == component.hashCode + + // if the component we found in the system already has data in it then we are going to add a new component + // if it doesn't then just overwrite it completely with the one from the library + + // regardless we need to set the id + component['@guid'] = short.generate() + + // we also need to create new @guid valuse inside the userValue in case they add multiple versions of the same + component = this.componentLibraryUpdateUserValueGuid(component) + + if (Object.keys(ptObjFound.userValue).length <= 1 || ptObjFound.id.includes("id_loc_gov_ontologies_bibframe_adminmetadata")) { + // if this is 1 or 0 then the userdata is empty, with just a @root property + // there is no user data added yet + // we can just overwrite whats there with our component + // we don't need to adjust the order, its 1-for-1 + // find it again and overwrite + + // Admin metadata should overwrite the existing values, otherwise we create a new Admin field, which we shouldn't do + if (!ptObjFound.id.includes("id_loc_gov_ontologies_bibframe_adminmetadata")) { + for (let pt in this.activeProfile.rt[component.parentId].pt) { + if (this.activeProfile.rt[component.parentId].pt[pt].id == component.id) { + this.activeProfile.rt[component.parentId].pt[pt] = JSON.parse(JSON.stringify(component)) + this.dataChanged() + return [component.parentId, pt] + } + } + } else { + // Need an exception for Admin to ensure we're not making changes to a different admin field + for (let pt in this.activeProfile.rt[component.parentId].pt) { + if (this.activeProfile.rt[component.parentId].pt[pt].id == ptObjFound.id) { + component.id = ptObjFound.id + this.activeProfile.rt[component.parentId].pt[pt] = JSON.parse(JSON.stringify(component)) + this.dataChanged() + return [component.parentId, pt] + } + } + } + + + } else { + // we can't replace the one that is there, already has data, so construct a new place for it + + // first find out how many of these components there are + let total_components = 0 + for (let pt in this.activeProfile.rt[component.parentId].pt) { + if (this.activeProfile.rt[component.parentId].pt[pt].id.startsWith(component.id)) { + total_components++ + } + } + let newId = component.id + "_" + (total_components + 1) + let oldId = JSON.parse(JSON.stringify(component.id)) + + // rename it + component.id = newId + // add it to the pt + this.activeProfile.rt[component.parentId].pt[newId] = JSON.parse(JSON.stringify(component)) + // add it to the order + // find the position of the last one + let insertAt = 0 + for (const [i, p] of this.activeProfile.rt[component.parentId].ptOrder.entries()) { + if (p.startsWith(oldId)) { + insertAt = i + } + } + this.activeProfile.rt[component.parentId].ptOrder.splice(insertAt + 1, 0, newId); + this.dataChanged() + return [component.parentId, newId] + } + + + + } else { + + alert("ERROR: There seems to be a mismatch between the component you are trying to add and the components in the profile. Please delete this component from your library and recreate it") + + } + + + } else { + alert("ERRROR: Could not find the orginal profile template this component was built from.", component.id) + } + + + } else { + alert("ERRROR: Trying to add a component but could not find the profile:", component.parentId) + } + + } + } + + } + + }, + + /** + * Reduce the components down to their structure for comparison + * @param {Object} componentExisting - Component that exists in the Profile + * @param {Object} componentLibrary - Component from library + */ + compareComponentStructure(componentExisting, componentLibrary) { + if (componentExisting.hasCode == componentLibrary.hashCode) { return true } + // strip out the non-structural stuff + let found = JSON.parse(JSON.stringify(componentExisting)) + delete found['@guid'] + delete found['hashCode'] + delete found['hashCodeId'] + delete found['userValue'] + delete found['canBeHidden'] + delete found['dataLoaded'] + delete found['deepHierarchy'] + delete found['hasData'] + delete found['remark'] + delete found['parent'] + delete found['xmlHash'] + delete found['xmlSource'] + delete found['userModified'] + delete found['activeType'] + delete found['missingProfile'] + delete found['valueConstraint']['editable'] + delete found['valueConstraint']['repeatable'] + delete found['valueConstraint']['valueDataType']['dataTypeLabel'] + delete found['valueConstraint']['valueDataType']['remark'] + delete found['valueConstraint']['defaults'] + delete found['refTemplateUserValue'] + delete found['refTemplateUserValueKeys'] + + let libraryComponent = JSON.parse(JSON.stringify(componentLibrary)) + delete libraryComponent['@guid'] + delete libraryComponent['hashCode'] + delete libraryComponent['hashCodeId'] + delete libraryComponent['userValue'] + delete libraryComponent['activeType'] + delete libraryComponent['canBeHidden'] + delete libraryComponent['dataLoaded'] + delete libraryComponent['deepHierarchy'] + delete libraryComponent['hasData'] + delete libraryComponent['remark'] + delete libraryComponent['parent'] + delete libraryComponent['userModified'] + delete libraryComponent['xmlHash'] + delete libraryComponent['xmlSource'] + delete libraryComponent['missingProfile'] + delete libraryComponent['valueConstraint']['editable'] + delete libraryComponent['valueConstraint']['repeatable'] + delete libraryComponent['valueConstraint']['valueDataType']['dataTypeLabel'] + delete libraryComponent['valueConstraint']['valueDataType']['remark'] + delete libraryComponent['valueConstraint']['defaults'] + delete libraryComponent['refTemplateUserValue'] + delete libraryComponent['refTemplateUserValueKeys'] + + if (found['valueConstraint']['valueDataType'] && Object.keys(found['valueConstraint']['valueDataType'].length > 0)) { + for (let key of Object.keys(found['valueConstraint']['valueDataType'])) { + if (found['valueConstraint']['valueDataType'][key] == "") { + delete found['valueConstraint']['valueDataType'][key] + } + } + } + + if (libraryComponent['valueConstraint']['valueDataType'] && Object.keys(libraryComponent['valueConstraint']['valueDataType'].length > 0)) { + for (let key of Object.keys(libraryComponent['valueConstraint']['valueDataType'])) { + if (libraryComponent['valueConstraint']['valueDataType'][key] == "") { + delete libraryComponent['valueConstraint']['valueDataType'][key] + } + } + } - /** - * - * @param {string} profileName - name of the profile the element belongs to - * @param {string} element - the id of the element that will be removed - */ - removeFromAdHocMode: function(profileName, element){ - if (this.emptyComponents[profileName].includes(element)){ - let idx = this.emptyComponents[profileName].indexOf(element) - this.emptyComponents[profileName].splice(idx, 1) - return true - } else { - console.warn("Couldn't find ", element, " in Ad Hoc mode: ", this.emptyComponents) - return false - } - }, - /** - * - * @param {string} guid - guid of the component - * @param {string} error - the error message - */ - addCammModeError: function(guid, error){ + // Some adjustments for Items + if (found['parentId'].includes("Item-")) { + found['parentId'] = found['parentId'].split('-')[0] + } + if (libraryComponent['parentId'].includes("Item-")) { + libraryComponent['parentId'] = libraryComponent['parentId'].split('-')[0] + } + + // Get everything into a reliable order + found.valueConstraint = Object.keys(found.valueConstraint).sort().reduce( + (obj, key) => { + obj[key] = found.valueConstraint[key]; + return obj; + }, + {} + ); + libraryComponent.valueConstraint = Object.keys(libraryComponent.valueConstraint).sort().reduce( + (obj, key) => { + obj[key] = libraryComponent.valueConstraint[key]; + return obj; + }, + {} + ); + let orderedFound = Object.keys(found).sort().reduce( + (obj, key) => { + obj[key] = found[key]; + return obj; + }, + {} + ); + let orderedLibrary = Object.keys(libraryComponent).sort().reduce( + (obj, key) => { + obj[key] = libraryComponent[key]; + return obj; + }, + {} + ); + + + // update uris to be more generic. + let ordereString = JSON.stringify(orderedFound) + let libraryString = JSON.stringify(orderedLibrary) + + ordereString = ordereString.replaceAll("//id.loc.gov", "//example.com") + libraryString = libraryString.replaceAll("//id.loc.gov", "//example.com") + + ordereString = ordereString.replaceAll("//preprod.id.loc.gov", "//example.com") + libraryString = libraryString.replaceAll("//preprod.id.loc.gov", "//example.com") + + orderedFound = JSON.parse(ordereString) + orderedLibrary = JSON.parse(libraryString) + + // console.info("existing: ", JSON.stringify(orderedFound)) + // console.info("library: ", JSON.stringify(orderedLibrary)) + + let orderedFoundHashCode = hashCode(JSON.stringify(orderedFound)) + let orderedLibraryHashCode = hashCode(JSON.stringify(orderedLibrary)) + + + // if the existing hashMatches the stripped down library hash + if (componentExisting.hashCode == orderedLibraryHashCode) { + return true + } else if (orderedFoundHashCode == orderedLibraryHashCode) { // if both stripped down components match + return true + } else { + return false + } - if (!this.cammModeErrors[guid]){ - this.cammModeErrors[guid]=[] - } - this.cammModeErrors[guid].push(error) + }, - }, - /** - * - * @param {string} guid - guid of the component - */ - clearCammModeError: function(guid){ - if (this.cammModeErrors[guid]){ - delete this.cammModeErrors[guid] - } - }, - /** - * Returns the marc label or auth label of the entitiy - * - * @param {object} guid - the guid of the component - */ - async returnCammComplexLabel(guid,complexValue){ - - console.log("guidguidguidguidguidguid",guid) - let pt = utilsProfile.returnPt(this.activeProfile,guid) - - // just look for the expected place to fidn the MARC key first - - if (pt && pt.userValue && pt.userValue[pt.propertyURI] && pt.userValue[pt.propertyURI][0]){ - for (let key of Object.keys(pt.userValue[pt.propertyURI][0])){ - let x = pt.userValue[pt.propertyURI][0][key] - if (x && x[0] && x[0]['http://id.loc.gov/ontologies/bflc/marcKey']){ - if (x[0]['http://id.loc.gov/ontologies/bflc/marcKey']){ - let marcKey = x[0]['http://id.loc.gov/ontologies/bflc/marcKey'] - if (marcKey[0] && marcKey[0]['http://id.loc.gov/ontologies/bflc/marcKey']){ - marcKey=marcKey[0]['http://id.loc.gov/ontologies/bflc/marcKey'] - } - if (marcKey.indexOf("$")>-1){ - marcKey = marcKey.slice(marcKey.indexOf("$")) - } - console.log("marcKeymarcKeymarcKeymarcKey",marcKey) - // return marcKey - } - } - } - } + componentLibraryUpdateUserValueGuid(component) { + function traverse(target) { + for (const key in target) { + if (typeof target[key] === 'object') { + traverse(target[key]); + } else if (key === '@guid') { + target[key] = short.generate() + } else { + // nothing + } + } + } + traverse(component.userValue); - if (complexValue && complexValue.URI){ - console.log("complexValuecomplexValuecomplexValuecomplexValue",complexValue) - // ask the internet - if (complexValue.URI.indexOf("id.loc.gov")>-1){ + return component + }, - let marcKey = await utilsNetwork.returnMARCKey(complexValue.URI + '.madsrdf_raw.jsonld') - if (marcKey && marcKey['marcKey']){ - marcKey = marcKey['marcKey'] - if (marcKey.indexOf("$")>-1){ - marcKey = marcKey.slice(marcKey.indexOf("$")) + /** Removes a component from the library + * + */ + delComponentLibrary(id) { + for (let key in this.componentLibrary.profiles) { + this.componentLibrary.profiles[key].groups = this.componentLibrary.profiles[key].groups.filter((c) => { return (c.id != id) }) } - return marcKey - } + this.saveComponentLibrary() + }, + /** Renames a component's label in the library + * + */ + renameComponentLibrary(id, newLabel) { + + for (let key in this.componentLibrary.profiles) { + for (let group of this.componentLibrary.profiles[key].groups) { + if (group.id == id) { + group.label = newLabel + this.saveComponentLibrary() + return true + } + } + } + }, + /** + * Set or unset the default nature of the component + */ + makeComponentDefault(id) { + for (let key in this.componentLibrary.profiles) { + for (let group of this.componentLibrary.profiles[key].groups) { + if (group.id == id) { + if (Object.keys(group).includes("useDefault")) { + group.useDefault = !group.useDefault + } else { + group["useDefault"] = true + } - } + this.saveComponentLibrary() + return true + } + } + } + }, + /** + * + * @param {string} profileName - name of the profile the element belongs to + * @param {string} element - the id of the element that will be added + */ + addToAdHocMode: function (profileName, element) { + let target = this.activeProfile.rt[profileName].pt[element] + let empty = this.isEmptyComponent(this.activeProfile.rt[profileName].pt[element]) + if (empty && target.mandatory != 'true') { + if (Object.keys(this.emptyComponents).includes(profileName)) { + this.emptyComponents[profileName].push(element) + } else { + this.emptyComponents[profileName] = [element] + } + } else { + console.warn(element, " is mandatory or populated. Didn't hide it.") + } + }, - } + /** + * + * @param {string} profileName - name of the profile the element belongs to + * @param {string} element - the id of the element that will be removed + */ + removeFromAdHocMode: function (profileName, element) { + if (this.emptyComponents[profileName].includes(element)) { + let idx = this.emptyComponents[profileName].indexOf(element) + this.emptyComponents[profileName].splice(idx, 1) + return true + } else { + console.warn("Couldn't find ", element, " in Ad Hoc mode: ", this.emptyComponents) + return false + } + }, - console.log(complexValue) + /** + * + * @param {string} guid - guid of the component + * @param {string} error - the error message + */ + addCammModeError: function (guid, error) { + if (!this.cammModeErrors[guid]) { + this.cammModeErrors[guid] = [] + } - return "Errr" + this.cammModeErrors[guid].push(error) + }, + /** + * + * @param {string} guid - guid of the component + */ + clearCammModeError: function (guid) { + if (this.cammModeErrors[guid]) { + delete this.cammModeErrors[guid] + } + }, + /** + * Returns the marc label or auth label of the entitiy + * + * @param {object} guid - the guid of the component + */ + async returnCammComplexLabel(guid, complexValue) { + + console.log("guidguidguidguidguidguid", guid) + let pt = utilsProfile.returnPt(this.activeProfile, guid) + + // just look for the expected place to fidn the MARC key first + + if (pt && pt.userValue && pt.userValue[pt.propertyURI] && pt.userValue[pt.propertyURI][0]) { + for (let key of Object.keys(pt.userValue[pt.propertyURI][0])) { + let x = pt.userValue[pt.propertyURI][0][key] + if (x && x[0] && x[0]['http://id.loc.gov/ontologies/bflc/marcKey']) { + if (x[0]['http://id.loc.gov/ontologies/bflc/marcKey']) { + let marcKey = x[0]['http://id.loc.gov/ontologies/bflc/marcKey'] + if (marcKey[0] && marcKey[0]['http://id.loc.gov/ontologies/bflc/marcKey']) { + marcKey = marcKey[0]['http://id.loc.gov/ontologies/bflc/marcKey'] + } + if (marcKey.indexOf("$") > -1) { + marcKey = marcKey.slice(marcKey.indexOf("$")) + } + console.log("marcKeymarcKeymarcKeymarcKey", marcKey) + // return marcKey + } + } + } + } + if (complexValue && complexValue.URI) { + console.log("complexValuecomplexValuecomplexValuecomplexValue", complexValue) + // ask the internet + if (complexValue.URI.indexOf("id.loc.gov") > -1) { - }, + let marcKey = await utilsNetwork.returnMARCKey(complexValue.URI + '.madsrdf_raw.jsonld') + if (marcKey && marcKey['marcKey']) { + marcKey = marcKey['marcKey'] + if (marcKey.indexOf("$") > -1) { + marcKey = marcKey.slice(marcKey.indexOf("$")) + } + return marcKey + } - /** - * Sorts an array of objects based on whether a specified key's value matches the latinRegex pattern. - * It sorts the array with the latin objects first, followed by the non-Latin objects. - * - * @param {Object[]} arr - Array of objects to sort - * @param {string} key - Object key whose value should be tested against latinRegex - * @return {Object[]} - Sorted array with non-Latin objects first - */ - sortObjectsByLatinMatch(arr, key) { - // let toSort = JSON.parse(JSON.stringify(arr)) - // if they have language tags in them then we know how to sort - // console.log(JSON.stringify(arr,null,2)) - // console.log(arr.filter((v)=>{ return (v['@language'])}).length) - // let sortedArry = [] - if (arr.filter((v)=>{ return (v['@language'])}).length >= 1){ - // // sort by language tags - arr.sort((a, b) => { - let aLang = a['@language'] || ''; - let bLang = b['@language'] || ''; + } - aLang = aLang.toLowerCase() - bLang = bLang.toLowerCase() - let aIsLatin = true - let bIsLatin = true - if (aLang.length > 0 && aLang.indexOf("-latn") == -1){ - aIsLatin = false - }else if (aLang.length > 0 && aLang.indexOf("-latn") > -1){ - aIsLatin = true - }else if (aLang.length == 0){ - aIsLatin = true - } + } - if (bLang.length > 0 && bLang.indexOf("-latn") == -1){ - bIsLatin = false - }else if (bLang.length > 0 && bLang.indexOf("-latn") > -1){ - bIsLatin = true - }else if (bLang.length == 0){ - bIsLatin = true - } + console.log(complexValue) - if (!bIsLatin && aIsLatin) return -1; - if (bIsLatin && !aIsLatin) return 1; - return 0; - }); + return "Errr" - }else{ - // no tags, try the regex - arr.sort((a, b) => { - // Handle cases where key doesn't exist - const aValue = a[key] || ''; - const bValue = b[key] || ''; - const aIsLatin = latinRegex.test(aValue); - const bIsLatin = latinRegex.test(bValue); + }, - if (!bIsLatin && aIsLatin) return -1; - if (bIsLatin && !aIsLatin) return 1; - return 0; - }); + /** + * Sorts an array of objects based on whether a specified key's value matches the latinRegex pattern. + * It sorts the array with the latin objects first, followed by the non-Latin objects. + * + * @param {Object[]} arr - Array of objects to sort + * @param {string} key - Object key whose value should be tested against latinRegex + * @return {Object[]} - Sorted array with non-Latin objects first + */ + sortObjectsByLatinMatch(arr, key) { + + // let toSort = JSON.parse(JSON.stringify(arr)) + // if they have language tags in them then we know how to sort + // console.log(JSON.stringify(arr,null,2)) + // console.log(arr.filter((v)=>{ return (v['@language'])}).length) + // let sortedArry = [] + if (arr.filter((v) => { return (v['@language']) }).length >= 1) { + // // sort by language tags + arr.sort((a, b) => { + let aLang = a['@language'] || ''; + let bLang = b['@language'] || ''; + + + aLang = aLang.toLowerCase() + bLang = bLang.toLowerCase() + let aIsLatin = true + let bIsLatin = true + + if (aLang.length > 0 && aLang.indexOf("-latn") == -1) { + aIsLatin = false + } else if (aLang.length > 0 && aLang.indexOf("-latn") > -1) { + aIsLatin = true + } else if (aLang.length == 0) { + aIsLatin = true + } - } + if (bLang.length > 0 && bLang.indexOf("-latn") == -1) { + bIsLatin = false + } else if (bLang.length > 0 && bLang.indexOf("-latn") > -1) { + bIsLatin = true + } else if (bLang.length == 0) { + bIsLatin = true + } - // if the array is larger than 2, we need to interleave the results - if (arr.length == 6){ - arr.splice(1, 0, arr.splice(3, 1)[0]); - arr.splice(3, 0, arr.splice(4, 1)[0]); - } - if (arr.length == 4){ - arr.splice(1, 0, arr.splice(2, 1)[0]); - } - // let fiftyPercent = Math.floor(sortedArry.length / 2) - // let largeAryOrder = [] - // console.log("sortedArry",sortedArry) - // console.log("fiftyPercent",fiftyPercent) - // for (let i = 0; i < fiftyPercent; i++) { - // console.log("i",i) - // console.log("i + fiftyPercent",i + fiftyPercent) + if (!bIsLatin && aIsLatin) return -1; - // largeAryOrder.push(JSON.parse(JSON.stringify(sortedArry[i]))); - // largeAryOrder.push(JSON.parse(JSON.stringify(sortedArry[i + fiftyPercent]))); - // console.log("largeAryOrder ->",JSON.parse(JSON.stringify(largeAryOrder))) - // } + if (bIsLatin && !aIsLatin) return 1; + return 0; + }); - // console.log("sortedArry!!!",sortedArry) - // console.log("Returning largeAryOrder:", largeAryOrder) - // return largeAryOrder + } else { - // }else{ - // console.log("Returning sortedArry",sortedArry) - // return sortedArry + // no tags, try the regex + arr.sort((a, b) => { + // Handle cases where key doesn't exist + const aValue = a[key] || ''; + const bValue = b[key] || ''; + const aIsLatin = latinRegex.test(aValue); + const bIsLatin = latinRegex.test(bValue); - // } + if (!bIsLatin && aIsLatin) return -1; + if (bIsLatin && !aIsLatin) return 1; + return 0; + }); + } + // if the array is larger than 2, we need to interleave the results + if (arr.length == 6) { + arr.splice(1, 0, arr.splice(3, 1)[0]); + arr.splice(3, 0, arr.splice(4, 1)[0]); + } + if (arr.length == 4) { + arr.splice(1, 0, arr.splice(2, 1)[0]); + } + // let fiftyPercent = Math.floor(sortedArry.length / 2) + // let largeAryOrder = [] + // console.log("sortedArry",sortedArry) + // console.log("fiftyPercent",fiftyPercent) + // for (let i = 0; i < fiftyPercent; i++) { + // console.log("i",i) + // console.log("i + fiftyPercent",i + fiftyPercent) + // largeAryOrder.push(JSON.parse(JSON.stringify(sortedArry[i]))); + // largeAryOrder.push(JSON.parse(JSON.stringify(sortedArry[i + fiftyPercent]))); + // console.log("largeAryOrder ->",JSON.parse(JSON.stringify(largeAryOrder))) + // } - return arr + // console.log("sortedArry!!!",sortedArry) - }, + // console.log("Returning largeAryOrder:", largeAryOrder) + // return largeAryOrder + // }else{ + // console.log("Returning sortedArry",sortedArry) + // return sortedArry - /** - * Extracts Library of Congress Classification (LCC) data from the active profile and updates the activeShelfListData state - * - * This function: - * 1. Searches through all resource templates (rt) and property templates (pt) in the active profile - * 2. Looks for classification properties of type ClassificationLcc - * 3. Extracts the classification portion (class number) and item portion (cutter number) - * 4. Updates the activeShelfListData state with: - * - class: The LCC class number - * - classGuid: GUID for the class number component - * - cutter: The cutter number - * - cutterGuid: GUID for the cutter number component - * - componentGuid: GUID of the parent classification component - * - componentPropertyPath: Path to locate the item portion property - * - * Used by the shelf listing functionality to locate and modify LCC numbers. - * - * - * @return {void} Updates the activeShelfListData state directly - */ - buildActiveShelfListDataFromProfile(){ - // look through the active profile for the LCC data - let classificationCount = 0 - let foundLCC = false - for (let rt in this.activeProfile.rt){ - for (let pt in this.activeProfile.rt[rt].pt){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/classification"){ - classificationCount++ - } - } - } - // console.log("classificationCount ===",classificationCount) + // } - for (let rt in this.activeProfile.rt){ - for (let pt in this.activeProfile.rt[rt].pt){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/classification"){ - // console.log(pt) - if (pt.userValue && - pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] && - pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0]['@type']){ - // if it is a LCC node good, and it is not deleted: - if (pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/ClassificationLcc" && !pt.deleted){ + return arr - let classObj = pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] - if ( - classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'] && - classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0] && - classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] - ){ - this.activeShelfListData.class = classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] - this.activeShelfListData.classGuid = classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['@guid'] - } - if ( - classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'] && - classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0] && - classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion'] - ){ - this.activeShelfListData.cutter = classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion'] - this.activeShelfListData.cutterGuid = classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['@guid'] + }, + + + /** + * Extracts Library of Congress Classification (LCC) data from the active profile and updates the activeShelfListData state + * + * This function: + * 1. Searches through all resource templates (rt) and property templates (pt) in the active profile + * 2. Looks for classification properties of type ClassificationLcc + * 3. Extracts the classification portion (class number) and item portion (cutter number) + * 4. Updates the activeShelfListData state with: + * - class: The LCC class number + * - classGuid: GUID for the class number component + * - cutter: The cutter number + * - cutterGuid: GUID for the cutter number component + * - componentGuid: GUID of the parent classification component + * - componentPropertyPath: Path to locate the item portion property + * + * Used by the shelf listing functionality to locate and modify LCC numbers. + * + * + * @return {void} Updates the activeShelfListData state directly + */ + buildActiveShelfListDataFromProfile() { + // look through the active profile for the LCC data + let classificationCount = 0 + let foundLCC = false + for (let rt in this.activeProfile.rt) { + for (let pt in this.activeProfile.rt[rt].pt) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/classification") { + classificationCount++ } + } + } + // console.log("classificationCount ===",classificationCount) - this.activeShelfListData.componentGuid = pt['@guid'] - this.activeShelfListData.componentPropertyPath = [ - {level: 0, propertyURI: 'http://id.loc.gov/ontologies/bibframe/classification'}, - {level: 1, propertyURI: 'http://id.loc.gov/ontologies/bibframe/itemPortion'} - ] + for (let rt in this.activeProfile.rt) { + for (let pt in this.activeProfile.rt[rt].pt) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == "http://id.loc.gov/ontologies/bibframe/classification") { + // console.log(pt) + if (pt.userValue && + pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] && + pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0]['@type']) { - // console.log("Found LCC data:",this.activeShelfListData) - // console.log(JSON.stringify(pt,null,2)) - foundLCC = true + // if it is a LCC node good, and it is not deleted: + if (pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0]['@type'] == "http://id.loc.gov/ontologies/bibframe/ClassificationLcc" && !pt.deleted) { - // this is what we want,break here to prevent hitting the second lcc if the recrd has two - break - }else{ - // not LCC - // continue - } + let classObj = pt.userValue['http://id.loc.gov/ontologies/bibframe/classification'][0] - }else{ + if ( + classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'] && + classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0] && + classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] + ) { + this.activeShelfListData.class = classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['http://id.loc.gov/ontologies/bibframe/classificationPortion'] + this.activeShelfListData.classGuid = classObj['http://id.loc.gov/ontologies/bibframe/classificationPortion'][0]['@guid'] + } + if ( + classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'] && + classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0] && + classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion'] + ) { + this.activeShelfListData.cutter = classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['http://id.loc.gov/ontologies/bibframe/itemPortion'] + this.activeShelfListData.cutterGuid = classObj['http://id.loc.gov/ontologies/bibframe/itemPortion'][0]['@guid'] + } - // if it is only one classification and it is empty then we can use it - // console.log(Object.keys(pt.userValue).length) - if (classificationCount == 1 && Object.keys(pt.userValue).length == 1){ - // console.log("Using empty classification for shelf list",pt) - this.activeShelfListData.componentGuid = pt['@guid'] - this.activeShelfListData.componentPropertyPath = [ - {level: 0, propertyURI: 'http://id.loc.gov/ontologies/bibframe/classification'}, - {level: 1, propertyURI: 'http://id.loc.gov/ontologies/bibframe/itemPortion'} - ] - foundLCC = true - - }else if (foundLCC == false && pt?.preferenceId.toLowerCase().indexOf(":lcc") > -1){ - // if we dont have one but we found a LCC template use it - this.activeShelfListData.componentGuid = pt['@guid'] - this.activeShelfListData.componentPropertyPath = [ - {level: 0, propertyURI: 'http://id.loc.gov/ontologies/bibframe/classification'}, - {level: 1, propertyURI: 'http://id.loc.gov/ontologies/bibframe/itemPortion'} - ] - foundLCC = true + this.activeShelfListData.componentGuid = pt['@guid'] + this.activeShelfListData.componentPropertyPath = [ + { level: 0, propertyURI: 'http://id.loc.gov/ontologies/bibframe/classification' }, + { level: 1, propertyURI: 'http://id.loc.gov/ontologies/bibframe/itemPortion' } + ] - } + // console.log("Found LCC data:",this.activeShelfListData) + // console.log(JSON.stringify(pt,null,2)) + foundLCC = true + // this is what we want,break here to prevent hitting the second lcc if the recrd has two + break - } + } else { + // not LCC + // continue + } - } - } - } - console.log("Final activeShelfListData:",this.activeShelfListData) + } else { + + // if it is only one classification and it is empty then we can use it + // console.log(Object.keys(pt.userValue).length) + if (classificationCount == 1 && Object.keys(pt.userValue).length == 1) { + // console.log("Using empty classification for shelf list",pt) + this.activeShelfListData.componentGuid = pt['@guid'] + this.activeShelfListData.componentPropertyPath = [ + { level: 0, propertyURI: 'http://id.loc.gov/ontologies/bibframe/classification' }, + { level: 1, propertyURI: 'http://id.loc.gov/ontologies/bibframe/itemPortion' } + ] + foundLCC = true + + } else if (foundLCC == false && pt?.preferenceId.toLowerCase().indexOf(":lcc") > -1) { + // if we dont have one but we found a LCC template use it + this.activeShelfListData.componentGuid = pt['@guid'] + this.activeShelfListData.componentPropertyPath = [ + { level: 0, propertyURI: 'http://id.loc.gov/ontologies/bibframe/classification' }, + { level: 1, propertyURI: 'http://id.loc.gov/ontologies/bibframe/itemPortion' } + ] + foundLCC = true - }, + } - reorderAllNonLatinLiterals(){ - this.activeProfile = utilsParse.reorderAllNonLatinLiterals(this.activeProfile) - }, + } - /** - * Tests if the passed string contains only Latin characters or includes non-Latin characters. - * - * @param {string} inputString - The string to test. - * @returns {boolean} - True if the string contains only Latin characters, false otherwise. - */ - isLatin(inputString) { - // Regex to match common Latin characters, numbers, punctuation, and extended Latin ranges. - return latinRegex.test(inputString); - }, - isTestEnv(){ - return window.location.href.includes("localhost:5555") - }, + } + } + } + console.log("Final activeShelfListData:", this.activeShelfListData) + }, - async buildLinkedData(){ + reorderAllNonLatinLiterals() { + this.activeProfile = utilsParse.reorderAllNonLatinLiterals(this.activeProfile) + }, - let linkedData = { - subtitle: [], - noteContent: [], - noteTOC: [], - thumbnail: [], - lcsh: [], - genre: [], - booksellerResults: [], - contributors: utilsProfile.returnContributorUris(this.activeProfile), - isbn: (this.activeProfile.linkedData && this.activeProfile.linkedData.isbn) ? this.activeProfile.linkedData.isbn : [], - } - // console.log(":linkedDatalinkedData",linkedData) + /** + * Tests if the passed string contains only Latin characters or includes non-Latin characters. + * + * @param {string} inputString - The string to test. + * @returns {boolean} - True if the string contains only Latin characters, false otherwise. + */ + isLatin(inputString) { + // Regex to match common Latin characters, numbers, punctuation, and extended Latin ranges. + return latinRegex.test(inputString); + }, - for (let isbn of linkedData.isbn){ + isTestEnv() { + return window.location.href.includes("localhost:5555") + }, - let baseData = await utilsNetwork.linkedDataBaseRelated(isbn) - // console.log("baseData",baseData) - let oclcMarcData = utilsNetwork.linkedDataExtractOclcMarc(baseData.results) - // console.log("oclcMarcData",oclcMarcData) - linkedData.subtitle = linkedData.subtitle.concat(oclcMarcData.filter((v) => (v.dataType == 'subtitle'))); - linkedData.noteContent = linkedData.noteContent.concat(oclcMarcData.filter((v) => (v.dataType == 'description'))); - linkedData.noteTOC = linkedData.noteTOC.concat(oclcMarcData.filter((v) => (v.dataType == 'toc'))); - linkedData.thumbnail = linkedData.thumbnail.concat(oclcMarcData.filter((v) => (v.dataType == 'thumbnail'))); - linkedData.lcsh = linkedData.lcsh.concat(oclcMarcData.filter((v) => (v.dataType == '6xx'))); - linkedData.genre = linkedData.genre.concat(oclcMarcData.filter((v) => (v.dataType == 'genre'))); + async buildLinkedData() { - linkedData.isbn = linkedData.isbn.concat(baseData.results.isbns); - linkedData.isbn.map((v)=>{ - if (v.length > 10){ - linkedData.isbn.push(utilsProfile.isbn13to10(v)) - } - }) - linkedData.isbn = [...new Set(linkedData.isbn)]; + let linkedData = { + subtitle: [], + noteContent: [], + noteTOC: [], + thumbnail: [], + lcsh: [], + genre: [], + booksellerResults: [], + contributors: utilsProfile.returnContributorUris(this.activeProfile), + isbn: (this.activeProfile.linkedData && this.activeProfile.linkedData.isbn) ? this.activeProfile.linkedData.isbn : [], + } + // console.log(":linkedDatalinkedData",linkedData) + for (let isbn of linkedData.isbn) { + let baseData = await utilsNetwork.linkedDataBaseRelated(isbn) + // console.log("baseData",baseData) + let oclcMarcData = utilsNetwork.linkedDataExtractOclcMarc(baseData.results) + // console.log("oclcMarcData",oclcMarcData) + linkedData.subtitle = linkedData.subtitle.concat(oclcMarcData.filter((v) => (v.dataType == 'subtitle'))); + linkedData.noteContent = linkedData.noteContent.concat(oclcMarcData.filter((v) => (v.dataType == 'description'))); + linkedData.noteTOC = linkedData.noteTOC.concat(oclcMarcData.filter((v) => (v.dataType == 'toc'))); + linkedData.thumbnail = linkedData.thumbnail.concat(oclcMarcData.filter((v) => (v.dataType == 'thumbnail'))); + linkedData.lcsh = linkedData.lcsh.concat(oclcMarcData.filter((v) => (v.dataType == '6xx'))); + linkedData.genre = linkedData.genre.concat(oclcMarcData.filter((v) => (v.dataType == 'genre'))); - if (baseData.results.isbns && baseData.results.isbns.length > 0){ - let googleBookData = await utilsNetwork.linkedDataAllGoogleBooksByIsbns(baseData.results.isbns) - googleBookData = utilsNetwork.linkedDataExtractGoogleBooks(googleBookData) - // console.log("googleBookData",googleBookData) + linkedData.isbn = linkedData.isbn.concat(baseData.results.isbns); - googleBookData.filter((v) => (v.dataType == 'toc')) + linkedData.isbn.map((v) => { + if (v.length > 10) { + linkedData.isbn.push(utilsProfile.isbn13to10(v)) + } + }) + linkedData.isbn = [...new Set(linkedData.isbn)]; - linkedData.subtitle = linkedData.subtitle.concat(googleBookData.filter((v) => (v.dataType == 'subtitle'))); - linkedData.noteContent = linkedData.noteContent.concat(googleBookData.filter((v) => (v.dataType == 'description'))); - linkedData.noteTOC = linkedData.noteTOC.concat(googleBookData.filter((v) => (v.dataType == 'toc'))); - linkedData.thumbnail = linkedData.thumbnail.concat(googleBookData.filter((v) => (v.dataType == 'thumbnail'))); - linkedData.lcsh = linkedData.lcsh.concat(googleBookData.filter((v) => (v.dataType == 'subject'))); - } - // if we got a hit on one of the isbns we can stop - if (baseData.results.records.length > 0){ - break - } + if (baseData.results.isbns && baseData.results.isbns.length > 0) { + let googleBookData = await utilsNetwork.linkedDataAllGoogleBooksByIsbns(baseData.results.isbns) + googleBookData = utilsNetwork.linkedDataExtractGoogleBooks(googleBookData) + // console.log("googleBookData",googleBookData) + googleBookData.filter((v) => (v.dataType == 'toc')) - } - // remove the $1 or $0 - for (let lcsh of linkedData.lcsh){ - lcsh.value = lcsh.value.filter((v) => (Object.keys(v)[0] != '1' && Object.keys(v)[0] != '0') ) - } + linkedData.subtitle = linkedData.subtitle.concat(googleBookData.filter((v) => (v.dataType == 'subtitle'))); + linkedData.noteContent = linkedData.noteContent.concat(googleBookData.filter((v) => (v.dataType == 'description'))); + linkedData.noteTOC = linkedData.noteTOC.concat(googleBookData.filter((v) => (v.dataType == 'toc'))); + linkedData.thumbnail = linkedData.thumbnail.concat(googleBookData.filter((v) => (v.dataType == 'thumbnail'))); + linkedData.lcsh = linkedData.lcsh.concat(googleBookData.filter((v) => (v.dataType == 'subject'))); - // remove trailing punctuation from genreForm - for (let genre of linkedData.genre){ - if (genre.value && genre.value.length > 0){ - genre.value = genre.value.replace(/[\.]$/, '') - } - } - - // unique the lcsh - let addedLCSH = [] - let keepLCSH = [] - for (let lcsh of linkedData.lcsh){ - let v = JSON.stringify(lcsh.value) - if (addedLCSH.indexOf(v) == -1){ - addedLCSH.push(v) - keepLCSH.push(lcsh) - } - } - - linkedData.lcsh=keepLCSH - - for (let key in linkedData) { - if (Array.isArray(linkedData[key])) { - const uniqueValues = new Set(); - linkedData[key] = linkedData[key].filter(item => { - if (item && typeof item.value !== 'undefined') { - if (!uniqueValues.has(item.value)) { - uniqueValues.add(item.value); - return true; - } - return false; - } - // Keep items that don't have a 'value' property or are not objects - return true; - }); - } - } - - // ask for contributor LCSH possbilbities - if (linkedData.contributors && linkedData.contributors.length > 0){ - let lcshContributors = await utilsNetwork.linkedDataLCSHContributors(linkedData.contributors) - // console.log("lcshContributors",lcshContributors) - // kick this off but don't wait for it to finish - utilsNetwork.linkedDataLCSHContributorsExtract(lcshContributors).then((colabResults)=>{ - // console.log("colabResults",colabResults) - }) - // if (lcshContributors && lcshContributors.length > 0){ - } - - // console.log("linkedData",linkedData) - this.linkedData = linkedData - this.linkedData.done = true - this.linkedData.eId = this.activeProfile && this.activeProfile.eId - }, + } + // if we got a hit on one of the isbns we can stop + if (baseData.results.records.length > 0) { + break + } - addLinkedData(toAdd){ - - if (toAdd.dataType == 'subtitle'){ - toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/title", "http://id.loc.gov/ontologies/bibframe/subtitle"] - toAdd.addNew = false - toAdd.resourceType = "instance" - } - if (toAdd.dataType == 'description' || toAdd.dataType == 'note'){ - toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/summary", "http://www.w3.org/2000/01/rdf-schema#label"] - toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/Summary" - toAdd.addNew = true - toAdd.resourceType = "work" - } - if (toAdd.dataType == 'toc' ){ - toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/tableOfContents", "http://www.w3.org/2000/01/rdf-schema#label"] - toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/TableOfContents" - toAdd.addNew = true - toAdd.resourceType = "work" - } - - if (toAdd.dataType == '6xx' ){ - toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/subject", "http://www.w3.org/2000/01/rdf-schema#label"] - toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/Subject" - toAdd.addNew = true - toAdd.resourceType = "work" - } - - if (toAdd.dataType == 'genre' ){ - toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/genreForm", "http://www.w3.org/2000/01/rdf-schema#label"] - toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/GenreForm" - toAdd.addNew = true - toAdd.resourceType = "work" - } - console.log("toAdd",toAdd) - for (let rt of Object.keys(this.activeProfile.rt)){ - if (rt.toLowerCase().includes(toAdd.resourceType)){ - for (let pt of Object.keys(this.activeProfile.rt[rt].pt)){ - pt = this.activeProfile.rt[rt].pt[pt] - if (pt.propertyURI == toAdd.PropertyPath[0]){ - - if (toAdd.addNew){ - - // find the matching property to copy from - let copyFrom = null - for (let ptLookingFor of Object.keys(this.activeProfile.rt[rt].pt)){ - if (this.activeProfile.rt[rt].pt[ptLookingFor].propertyURI == toAdd.PropertyPath[0]){ - copyFrom = this.activeProfile.rt[rt].pt[ptLookingFor] - copyFrom = JSON.parse(JSON.stringify(copyFrom)) - copyFrom['@guid'] = short.generate() - copyFrom.id = copyFrom.id + "_" + short.generate() - - - if (toAdd.PropertyPath[0] != "http://id.loc.gov/ontologies/bibframe/subject" && toAdd.PropertyPath[0] != "http://id.loc.gov/ontologies/bibframe/genreForm"){ - copyFrom.userValue = {'@root': toAdd.PropertyPath[0]} - copyFrom.userValue[toAdd.PropertyPath[0]] = [{ - '@guid': short.generate(), - '@type': toAdd.typeOf, - [toAdd.PropertyPath[1]]: [{ - '@guid': short.generate(), - [toAdd.PropertyPath[1]]: toAdd.value - }] - }] - let newIndex = this.activeProfile.rt[rt].ptOrder.indexOf(this.activeProfile.rt[rt].pt[ptLookingFor].id) - if (newIndex){ - newIndex++ - this.activeProfile.rt[rt].ptOrder.splice(newIndex, 0, copyFrom.id); - this.activeProfile.rt[rt].pt[copyFrom.id] = copyFrom; - this.dataChanged() - this.activeComponent = this.activeProfile.rt[rt].pt[copyFrom.id] - } - break - }else{ + } + // remove the $1 or $0 + for (let lcsh of linkedData.lcsh) { + lcsh.value = lcsh.value.filter((v) => (Object.keys(v)[0] != '1' && Object.keys(v)[0] != '0')) + } - // do something special for subjects - console.log("copyFrom",copyFrom) - copyFrom.userValue = {'@root': toAdd.PropertyPath[0], '@guid': short.generate()} + // remove trailing punctuation from genreForm + for (let genre of linkedData.genre) { + if (genre.value && genre.value.length > 0) { + genre.value = genre.value.replace(/[\.]$/, '') + } + } - // insert the new one and then look for it - let newIndex = this.activeProfile.rt[rt].ptOrder.indexOf(this.activeProfile.rt[rt].pt[ptLookingFor].id) - if (newIndex){ - newIndex++ - this.activeProfile.rt[rt].ptOrder.splice(newIndex, 0, copyFrom.id); - this.activeProfile.rt[rt].pt[copyFrom.id] = copyFrom; - this.dataChanged() - this.activeComponent = this.activeProfile.rt[rt].pt[copyFrom.id] + // unique the lcsh + let addedLCSH = [] + let keepLCSH = [] + for (let lcsh of linkedData.lcsh) { + let v = JSON.stringify(lcsh.value) + if (addedLCSH.indexOf(v) == -1) { + addedLCSH.push(v) + keepLCSH.push(lcsh) + } + } - setTimeout(()=>{ + linkedData.lcsh = keepLCSH - let foundEl = document.querySelector(`[data-field-guid="${copyFrom['@guid']}"]`) - foundEl.focus() - console.log("toAdd.value",toAdd.value) + for (let key in linkedData) { + if (Array.isArray(linkedData[key])) { + const uniqueValues = new Set(); + linkedData[key] = linkedData[key].filter(item => { + if (item && typeof item.value !== 'undefined') { + if (!uniqueValues.has(item.value)) { + uniqueValues.add(item.value); + return true; + } + return false; + } + // Keep items that don't have a 'value' property or are not objects + return true; + }); + } + } - let toAddString - if (toAdd.PropertyPath[0] == "http://id.loc.gov/ontologies/bibframe/subject"){ - toAddString = toAdd.value.map((v)=>{ return '$' + Object.keys(v)[0] + v[Object.keys(v)[0]] }).join("") - }else if (toAdd.PropertyPath[0] == "http://id.loc.gov/ontologies/bibframe/genreForm"){ - toAddString = toAdd.value - } + // ask for contributor LCSH possbilbities + if (linkedData.contributors && linkedData.contributors.length > 0) { + let lcshContributors = await utilsNetwork.linkedDataLCSHContributors(linkedData.contributors) + // console.log("lcshContributors",lcshContributors) + // kick this off but don't wait for it to finish + utilsNetwork.linkedDataLCSHContributorsExtract(lcshContributors).then((colabResults) => { + // console.log("colabResults",colabResults) + }) + // if (lcshContributors && lcshContributors.length > 0){ + } + // console.log("linkedData",linkedData) + this.linkedData = linkedData + this.linkedData.done = true + this.linkedData.eId = this.activeProfile && this.activeProfile.eId - setTimeout(()=>{ + }, - // const eventA = new Event('input', { - // data:'helloo', - // bubbles: true, - // }); - // console.log("Sending eventA",eventA) - // foundEl.dispatchEvent(eventA); - // foundEl.value="Hellooo" - const event = new Event('input', { bubbles: true }); - foundEl.value = toAddString; - foundEl.dispatchEvent(event); + addLinkedData(toAdd) { + + if (toAdd.dataType == 'subtitle') { + toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/title", "http://id.loc.gov/ontologies/bibframe/subtitle"] + toAdd.addNew = false + toAdd.resourceType = "instance" + } + if (toAdd.dataType == 'description' || toAdd.dataType == 'note') { + toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/summary", "http://www.w3.org/2000/01/rdf-schema#label"] + toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/Summary" + toAdd.addNew = true + toAdd.resourceType = "work" + } + if (toAdd.dataType == 'toc') { + toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/tableOfContents", "http://www.w3.org/2000/01/rdf-schema#label"] + toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/TableOfContents" + toAdd.addNew = true + toAdd.resourceType = "work" + } + + if (toAdd.dataType == '6xx') { + toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/subject", "http://www.w3.org/2000/01/rdf-schema#label"] + toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/Subject" + toAdd.addNew = true + toAdd.resourceType = "work" + } + if (toAdd.dataType == 'genre') { + toAdd.PropertyPath = ["http://id.loc.gov/ontologies/bibframe/genreForm", "http://www.w3.org/2000/01/rdf-schema#label"] + toAdd.typeOf = "http://id.loc.gov/ontologies/bibframe/GenreForm" + toAdd.addNew = true + toAdd.resourceType = "work" + } + console.log("toAdd", toAdd) + for (let rt of Object.keys(this.activeProfile.rt)) { + if (rt.toLowerCase().includes(toAdd.resourceType)) { + for (let pt of Object.keys(this.activeProfile.rt[rt].pt)) { + pt = this.activeProfile.rt[rt].pt[pt] + if (pt.propertyURI == toAdd.PropertyPath[0]) { - },500) + if (toAdd.addNew) { + // find the matching property to copy from + let copyFrom = null + for (let ptLookingFor of Object.keys(this.activeProfile.rt[rt].pt)) { + if (this.activeProfile.rt[rt].pt[ptLookingFor].propertyURI == toAdd.PropertyPath[0]) { + copyFrom = this.activeProfile.rt[rt].pt[ptLookingFor] + copyFrom = JSON.parse(JSON.stringify(copyFrom)) + copyFrom['@guid'] = short.generate() + copyFrom.id = copyFrom.id + "_" + short.generate() - console.log("foundEl",foundEl) + if (toAdd.PropertyPath[0] != "http://id.loc.gov/ontologies/bibframe/subject" && toAdd.PropertyPath[0] != "http://id.loc.gov/ontologies/bibframe/genreForm") { + copyFrom.userValue = { '@root': toAdd.PropertyPath[0] } + copyFrom.userValue[toAdd.PropertyPath[0]] = [{ + '@guid': short.generate(), + '@type': toAdd.typeOf, + [toAdd.PropertyPath[1]]: [{ + '@guid': short.generate(), + [toAdd.PropertyPath[1]]: toAdd.value + }] + }] - },250) + let newIndex = this.activeProfile.rt[rt].ptOrder.indexOf(this.activeProfile.rt[rt].pt[ptLookingFor].id) + if (newIndex) { + newIndex++ + this.activeProfile.rt[rt].ptOrder.splice(newIndex, 0, copyFrom.id); + this.activeProfile.rt[rt].pt[copyFrom.id] = copyFrom; + this.dataChanged() + this.activeComponent = this.activeProfile.rt[rt].pt[copyFrom.id] + } + break + } else { - } + // do something special for subjects + console.log("copyFrom", copyFrom) + copyFrom.userValue = { '@root': toAdd.PropertyPath[0], '@guid': short.generate() } - break - } - } - } + // insert the new one and then look for it + let newIndex = this.activeProfile.rt[rt].ptOrder.indexOf(this.activeProfile.rt[rt].pt[ptLookingFor].id) + if (newIndex) { + newIndex++ + this.activeProfile.rt[rt].ptOrder.splice(newIndex, 0, copyFrom.id); + this.activeProfile.rt[rt].pt[copyFrom.id] = copyFrom; + this.dataChanged() + this.activeComponent = this.activeProfile.rt[rt].pt[copyFrom.id] + setTimeout(() => { + let foundEl = document.querySelector(`[data-field-guid="${copyFrom['@guid']}"]`) + foundEl.focus() + console.log("toAdd.value", toAdd.value) + + let toAddString + if (toAdd.PropertyPath[0] == "http://id.loc.gov/ontologies/bibframe/subject") { + toAddString = toAdd.value.map((v) => { return '$' + Object.keys(v)[0] + v[Object.keys(v)[0]] }).join("") + } else if (toAdd.PropertyPath[0] == "http://id.loc.gov/ontologies/bibframe/genreForm") { + toAddString = toAdd.value + } + + + setTimeout(() => { + + // const eventA = new Event('input', { + // data:'helloo', + // bubbles: true, + + // }); + // console.log("Sending eventA",eventA) + // foundEl.dispatchEvent(eventA); + // foundEl.value="Hellooo" + const event = new Event('input', { bubbles: true }); + foundEl.value = toAddString; + foundEl.dispatchEvent(event); + + + }, 500) - }else{ + console.log("foundEl", foundEl) + + }, 250) + + } + + break + } + } + } + - // does it already have the property - if (pt.userValue - && pt.userValue[toAdd.PropertyPath[0]] - && pt.userValue[toAdd.PropertyPath[0]][0] - && !pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]] - ){ - console.log("it does not has the property",pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]]) - // doesn't have the prperty so create it wholelly - let uv = { - '@guid': short.generate() - } - uv[toAdd.PropertyPath[1]] = toAdd.value - pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]] = [uv] + } else { - }else if (pt.userValue - && pt.userValue[toAdd.PropertyPath[0]] - && pt.userValue[toAdd.PropertyPath[0]][0] - && pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]] - ){ - let uv = { - '@guid': short.generate(), - 'http://id.loc.gov/ontologies/bibframe/subtitle': toAdd.value + + // does it already have the property + if (pt.userValue + && pt.userValue[toAdd.PropertyPath[0]] + && pt.userValue[toAdd.PropertyPath[0]][0] + && !pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]] + ) { + console.log("it does not has the property", pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]]) + // doesn't have the prperty so create it wholelly + + let uv = { + '@guid': short.generate() + } + uv[toAdd.PropertyPath[1]] = toAdd.value + pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]] = [uv] + + } else if (pt.userValue + && pt.userValue[toAdd.PropertyPath[0]] + && pt.userValue[toAdd.PropertyPath[0]][0] + && pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]] + ) { + + let uv = { + '@guid': short.generate(), + 'http://id.loc.gov/ontologies/bibframe/subtitle': toAdd.value + } + pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]].push(uv) + } + this.dataChanged() + } + break + } } - pt.userValue[toAdd.PropertyPath[0]][0][toAdd.PropertyPath[1]].push(uv) - } - this.dataChanged() - } - break + } + // for (let pt in state.activeProfile.rt[rt].pt){ + // if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid){ + // return state.activeProfile.rt[rt].pt[pt] + // } + // } } - } - } - // for (let pt in state.activeProfile.rt[rt].pt){ - // if (state.activeProfile.rt[rt].pt[pt]['@guid'] === guid){ - // return state.activeProfile.rt[rt].pt[pt] - // } - // } - } - }, + }, - isValidIsbn(isbn){ - // source: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s13.html - // Checks for ISBN-10 or ISBN-13 format - var regex = /^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$/; - - if (regex.test(isbn)) { - // Remove non ISBN digits, then split into an array - var chars = isbn.replace(/[- ]|^ISBN(?:-1[03])?:?/g, "").split(""); - // Remove the final ISBN digit from `chars`, and assign it to `last` - var last = chars.pop(); - var sum = 0; - var check, i; - - if (chars.length == 9) { - // Compute the ISBN-10 check digit - chars.reverse(); - for (i = 0; i < chars.length; i++) { - sum += (i + 2) * parseInt(chars[i], 10); - } - check = 11 - (sum % 11); - if (check == 10) { - check = "X"; - } else if (check == 11) { - check = "0"; - } - } else { - // Compute the ISBN-13 check digit - for (i = 0; i < chars.length; i++) { - sum += (i % 2 * 2 + 1) * parseInt(chars[i], 10); - } - check = 10 - (sum % 10); - if (check == 10) { - check = "0"; + isValidIsbn(isbn) { + // source: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s13.html + // Checks for ISBN-10 or ISBN-13 format + var regex = /^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$/; + + if (regex.test(isbn)) { + // Remove non ISBN digits, then split into an array + var chars = isbn.replace(/[- ]|^ISBN(?:-1[03])?:?/g, "").split(""); + // Remove the final ISBN digit from `chars`, and assign it to `last` + var last = chars.pop(); + var sum = 0; + var check, i; + + if (chars.length == 9) { + // Compute the ISBN-10 check digit + chars.reverse(); + for (i = 0; i < chars.length; i++) { + sum += (i + 2) * parseInt(chars[i], 10); + } + check = 11 - (sum % 11); + if (check == 10) { + check = "X"; + } else if (check == 11) { + check = "0"; + } + } else { + // Compute the ISBN-13 check digit + for (i = 0; i < chars.length; i++) { + sum += (i % 2 * 2 + 1) * parseInt(chars[i], 10); + } + check = 10 - (sum % 10); + if (check == 10) { + check = "0"; + } } - } - if (check == last) { - return true + if (check == last) { + return true + } else { + return false + } } else { return false } - } else { - return false - } - }, - - checkIsRepeatable(comp){ - // if the field is not repeatable and they are adding something, prevent it. - let currentValue = this.returnSimpleLookupValueFromProfile(comp.guid, comp.propertyPath) - if (comp.structure.repeatable == 'false' && currentValue.length > 0){ - return false - } - return true - }, + }, - displayClassNumber: function(comp){ - let pref = usePreferenceStore().returnValue("--b-edit-main-hide-non-lc-class-numbers") - if (!pref){ return true } - - try { - if (comp.propertyURI == "http://id.loc.gov/ontologies/bibframe/classification"){ - let userValue = comp.userValue - let data = userValue["http://id.loc.gov/ontologies/bibframe/classification"] ? userValue["http://id.loc.gov/ontologies/bibframe/classification"][0] : {} - let type = data['@type'] - // check type - if (type == "http://id.loc.gov/ontologies/bibframe/ClassificationLcc" || type == "http://id.loc.gov/ontologies/bibframe/ClassificationDdc"){ + checkIsRepeatable(comp) { + // if the field is not repeatable and they are adding something, prevent it. + let currentValue = this.returnSimpleLookupValueFromProfile(comp.guid, comp.propertyPath) + if (comp.structure.repeatable == 'false' && currentValue.length > 0) { + return false + } return true - } - // fallback on assigner - if (data["http://id.loc.gov/ontologies/bibframe/assigner"]){ - let assigner = data["http://id.loc.gov/ontologies/bibframe/assigner"][0] - let id = assigner['@id'] - let label = assigner["http://www.w3.org/2000/01/rdf-schema#label"][0]["http://www.w3.org/2000/01/rdf-schema#label"] + }, + + displayClassNumber: function (comp) { + let pref = usePreferenceStore().returnValue("--b-edit-main-hide-non-lc-class-numbers") + if (!pref) { return true } + + try { + if (comp.propertyURI == "http://id.loc.gov/ontologies/bibframe/classification") { + let userValue = comp.userValue + let data = userValue["http://id.loc.gov/ontologies/bibframe/classification"] ? userValue["http://id.loc.gov/ontologies/bibframe/classification"][0] : {} + let type = data['@type'] + // check type + if (type == "http://id.loc.gov/ontologies/bibframe/ClassificationLcc" || type == "http://id.loc.gov/ontologies/bibframe/ClassificationDdc") { + return true + } + // fallback on assigner + if (data["http://id.loc.gov/ontologies/bibframe/assigner"]) { + let assigner = data["http://id.loc.gov/ontologies/bibframe/assigner"][0] + let id = assigner['@id'] + let label = assigner["http://www.w3.org/2000/01/rdf-schema#label"][0]["http://www.w3.org/2000/01/rdf-schema#label"] + + if (!label.includes("Library of Congress")) { + return false + } + } else { + return false + } + } - if (!label.includes("Library of Congress")){ - return false + return true + } catch (err) { + console.error("Error with displayClass preference: ", err) + return false } - } else { - return false - } - } + }, - return true - } catch(err){ - console.error("Error with displayClass preference: ", err) - return false - } - }, + displaySubject: function (comp) { + // if the preference is set to only show LCSH terms, return true/false based on the source + // how to handle if everything is hidden?? + // need to show something, or show that things are hidden and + // should it be like when ad hoc hides components? + let pref = usePreferenceStore().returnValue("--b-edit-main-hide-non-lc") + if (!pref) { return true } + try { + if (comp.propertyURI == "http://id.loc.gov/ontologies/bibframe/subject") { + + let userValue = comp.userValue + let data = userValue["http://id.loc.gov/ontologies/bibframe/subject"] ? userValue["http://id.loc.gov/ontologies/bibframe/subject"][0] : {} + if (data['@id'] && data['@id'].includes('http://id.loc.gov/authorities/')) { + return true + } + if (data["http://id.loc.gov/ontologies/bibframe/source"]) { + let source = data["http://id.loc.gov/ontologies/bibframe/source"][0] + let code = source["http://id.loc.gov/ontologies/bibframe/code"] ? source["http://id.loc.gov/ontologies/bibframe/code"][0]["http://id.loc.gov/ontologies/bibframe/code"] : "" + let label = source["http://www.w3.org/2000/01/rdf-schema#label"] ? source["http://www.w3.org/2000/01/rdf-schema#label"][0]["http://www.w3.org/2000/01/rdf-schema#label"] : "" + let sourceURI = source["@id"] ? source["@id"] : "" + + if (!label.includes("Library of Congress") && !label.includes("Children's and Young Adults")) { + return false + } + } else if (Object.keys(data).length == 2) { + return false + } + } - displaySubject: function(comp){ - // if the preference is set to only show LCSH terms, return true/false based on the source - // how to handle if everything is hidden?? - // need to show something, or show that things are hidden and - // should it be like when ad hoc hides components? - let pref = usePreferenceStore().returnValue("--b-edit-main-hide-non-lc") - if (!pref){ return true } - try { - if (comp.propertyURI == "http://id.loc.gov/ontologies/bibframe/subject"){ - - let userValue = comp.userValue - let data = userValue["http://id.loc.gov/ontologies/bibframe/subject"] ? userValue["http://id.loc.gov/ontologies/bibframe/subject"][0] : {} - if (data['@id'] && data['@id'].includes('http://id.loc.gov/authorities/')){ - return true - } - if (data["http://id.loc.gov/ontologies/bibframe/source"]){ - let source = data["http://id.loc.gov/ontologies/bibframe/source"][0] - let code = source["http://id.loc.gov/ontologies/bibframe/code"] ? source["http://id.loc.gov/ontologies/bibframe/code"][0]["http://id.loc.gov/ontologies/bibframe/code"] : "" - let label = source["http://www.w3.org/2000/01/rdf-schema#label"] ? source["http://www.w3.org/2000/01/rdf-schema#label"][0]["http://www.w3.org/2000/01/rdf-schema#label"] : "" - let sourceURI = source["@id"] ? source["@id"] : "" + return true + } catch (err) { + console.error("Error with displaySubject preference: ", err) + return true + } + }, + + /** + * Track number of hidden components to make sure to create an empty one if needed + * @param {Object} profile - Profile loaded + * @returns + */ + numberHiddenShown: function (profile) { + let subjectCount = 0 + let subjectHidden = 0 + let subjectLast = null + let subjectFirst = false + let subjectList = [] + let subjectProfile = null + + let classCount = 0 + let classHidden = 0 + let classLast = null + let classFirst = false + let classList = [] + let classProfile = null + + for (let rt in profile.rt) { + for (let pt in profile.rt[rt].pt) { + + if (pt.includes("id_loc_gov_ontologies_bibframe_subject__subjects")) { + if (!subjectFirst) { + subjectFirst = pt + } + let comp = profile.rt[rt].pt[pt] + if (comp.deleted === undefined || (Object.keys(comp).includes('deleted') && comp.deleted != true)) { + subjectCount++ + } + if (comp.hideSubject) { + subjectHidden++ + } + subjectLast = comp + subjectList.push(comp) + subjectProfile = rt + } - if (!label.includes("Library of Congress") && !label.includes("Children's and Young Adults")){ - return false + if (pt.includes("id_loc_gov_ontologies_bibframe_classification__classification_numbers")) { + if (!classFirst) { + classFirst = pt + } + let comp = profile.rt[rt].pt[pt] + if (comp.deleted === undefined || (Object.keys(comp).includes('deleted') && comp.deleted != true)) { + classCount++ + } + if (comp.hideClassNum) { + classHidden++ + } + classLast = comp + classList.push(comp) + classProfile = rt + } + } } - } else if(Object.keys(data).length == 2) { - return false - } - } - return true - } catch(err){ - console.error("Error with displaySubject preference: ", err) - return true - } - }, + let showingSubjects = subjectCount - subjectHidden + if (showingSubjects == 0) { + // add an empty subject component + // componentGuid, structure + this.duplicateComponent(subjectLast["@guid"], this.returnStructureByGUID(subjectLast["@guid"])) - /** - * Track number of hidden components to make sure to create an empty one if needed - * @param {Object} profile - Profile loaded - * @returns - */ - numberHiddenShown: function(profile){ - let subjectCount = 0 - let subjectHidden = 0 - let subjectLast = null - let subjectFirst = false - let subjectList = [] - let subjectProfile = null - - let classCount = 0 - let classHidden = 0 - let classLast = null - let classFirst = false - let classList = [] - let classProfile = null - - for (let rt in profile.rt){ - for (let pt in profile.rt[rt].pt){ - - if (pt.includes("id_loc_gov_ontologies_bibframe_subject__subjects")){ - if (!subjectFirst){ - subjectFirst = pt - } - let comp = profile.rt[rt].pt[pt] - if (comp.deleted === undefined || (Object.keys(comp).includes('deleted') && comp.deleted != true)){ - subjectCount++ - } - if (comp.hideSubject){ - subjectHidden++ - } - subjectLast = comp - subjectList.push(comp) - subjectProfile = rt - } - - if (pt.includes("id_loc_gov_ontologies_bibframe_classification__classification_numbers")){ - if (!classFirst){ - classFirst = pt - } - let comp = profile.rt[rt].pt[pt] - if (comp.deleted === undefined || (Object.keys(comp).includes('deleted') && comp.deleted != true)){ - classCount++ - } - if (comp.hideClassNum){ - classHidden++ - } - classLast = comp - classList.push(comp) - classProfile = rt - } - } - } + // make sure this new component is first + let posFirst = profile.rt[subjectProfile].ptOrder.indexOf(subjectFirst) + let posLast = profile.rt[subjectProfile].ptOrder.indexOf(subjectLast.id) + let newPropertyId = profile.rt[subjectProfile].ptOrder.at(posLast + 1) + profile.rt[subjectProfile].ptOrder.splice(Number(posFirst), 0, newPropertyId) - let showingSubjects = subjectCount - subjectHidden - if (showingSubjects == 0){ - // add an empty subject component - // componentGuid, structure - this.duplicateComponent(subjectLast["@guid"], this.returnStructureByGUID(subjectLast["@guid"])) + // remove the last subject + profile.rt[subjectProfile].ptOrder.splice(posLast + 2, 1) - // make sure this new component is first - let posFirst = profile.rt[subjectProfile].ptOrder.indexOf(subjectFirst) - let posLast = profile.rt[subjectProfile].ptOrder.indexOf(subjectLast.id) - let newPropertyId = profile.rt[subjectProfile].ptOrder.at(posLast+1) - profile.rt[subjectProfile].ptOrder.splice(Number(posFirst), 0, newPropertyId) + } - // remove the last subject - profile.rt[subjectProfile].ptOrder.splice(posLast+2, 1) + let showingClassNumber = classCount - classHidden + if (showingClassNumber == 0) { + // add an empty classNum component + // componentGuid, structure + this.duplicateComponent(classLast["@guid"], this.returnStructureByGUID(classLast["@guid"])) - } + // make sure this new component is first + let posFirst = profile.rt[classProfile].ptOrder.indexOf(classFirst) + let posLast = profile.rt[classProfile].ptOrder.indexOf(classLast.id) + let newPropertyId = profile.rt[classProfile].ptOrder.at(posLast + 1) - let showingClassNumber = classCount - classHidden - if (showingClassNumber == 0){ - // add an empty classNum component - // componentGuid, structure - this.duplicateComponent(classLast["@guid"], this.returnStructureByGUID(classLast["@guid"])) - // make sure this new component is first - let posFirst = profile.rt[classProfile].ptOrder.indexOf(classFirst) - let posLast = profile.rt[classProfile].ptOrder.indexOf(classLast.id) - let newPropertyId = profile.rt[classProfile].ptOrder.at(posLast+1) + profile.rt[classProfile].ptOrder.splice(Number(posFirst), 0, newPropertyId) + // remove the last classNum + profile.rt[classProfile].ptOrder.splice(posLast + 2, 1) + } - profile.rt[classProfile].ptOrder.splice(Number(posFirst), 0, newPropertyId) + if (subjectHidden > 0) { + this.hiddenSubjects = true + } + if (classHidden > 0) { + this.hiddenClassNumbers = true + } - // remove the last classNum - profile.rt[classProfile].ptOrder.splice(posLast+2, 1) - } + let results = { + 'subjects': subjectCount, 'hiddenSubject': subjectHidden, 'showingSubjects': showingSubjects, + 'classNumbers': classCount, 'hiddenClassNumbers': classHidden, 'showingClassNumbers': showingClassNumber + } - if (subjectHidden > 0){ - this.hiddenSubjects = true - } - if (classHidden > 0){ - this.hiddenClassNumbers = true - } + return results + }, - let results = { - 'subjects': subjectCount, 'hiddenSubject': subjectHidden, 'showingSubjects': showingSubjects, - 'classNumbers': classCount, 'hiddenClassNumbers': classHidden, 'showingClassNumbers': showingClassNumber - } + // ---------------------------UNDO STUFF BELOW HERE-------------------------------------- + saveState: function (profile = false) { + // profile is populated when the record is loaded from the URL + if (!profile) { + profile = JSON.stringify(this.activeProfile) + } else { + profile = JSON.stringify(profile) + } - return results - }, + if (this.currentState) { + if (this.undoRecords.length < this.undoRedoLimit) { + this.undoRecords.push(this.currentState) + } else { // remove the oldest profile + this.undoRecords.shift() + this.undoRecords.push(this.currentState) + } + } - // ---------------------------UNDO STUFF BELOW HERE-------------------------------------- - saveState: function(profile=false){ - // profile is populated when the record is loaded from the URL - if (!profile){ - profile = JSON.stringify(this.activeProfile) - } else { - profile = JSON.stringify(profile) - } - - if (this.currentState){ - if (this.undoRecords.length < this.undoRedoLimit){ - this.undoRecords.push(this.currentState) - } else { // remove the oldest profile - this.undoRecords.shift() - this.undoRecords.push(this.currentState) - } - } + this.currentState = profile - this.currentState = profile + }, - }, + undoChange: async function () { + if (this.undoRecords.length < 1) { + alert("Nothing to undo. We can't go back anymore.") + return + } + let profile = JSON.stringify(this.activeProfile) - undoChange: async function(){ - if (this.undoRecords.length < 1){ - alert("Nothing to undo. We can't go back anymore.") - return - } - let profile = JSON.stringify(this.activeProfile) - - // clear the cache - cachePt = {} - cacheGuid = {} - - // go back - let last = this.undoRecords.pop() - this.activeProfile = JSON.parse(last) - - // save the profile to redo - if (this.redoRecords.length < this.undoRedoLimit){ - this.redoRecords.push(profile) - } else { // remove the oldest profile - this.redoRecords.shift() - this.redoRecords.push(profile) - } - // trigger xml refresh - this.dataChangedTimestamp = Date.now() - - this.activeProfileSaved = false - if (usePreferenceStore().returnValue('--b-general-auto-save')){ - this.saveRecord() - } - }, + // clear the cache + cachePt = {} + cacheGuid = {} - redoChange: async function(){ - if (this.redoRecords.length < 1){ - alert("Nothing to redo. We can't go forward anymore.") - return - } - // let profile = JSON.stringify(this.activeProfile) - this.currentState = JSON.stringify(this.activeProfile) - - // save the profile to undo - if (this.undoRecords.length < this.undoRedoLimit){ - this.undoRecords.push(this.currentState) - } else { // remove the oldest profile - this.undoRecords.shift() - this.undoRecords.push(this.currentState) - } - - // clear the cache - cachePt = {} - cacheGuid = {} - let last = this.redoRecords.pop() - this.activeProfile = JSON.parse(last) - - // trigger xml refresh - this.dataChangedTimestamp = Date.now() - - this.activeProfileSaved = false - if (usePreferenceStore().returnValue('--b-general-auto-save')){ - this.saveRecord() - } - }, + // go back + let last = this.undoRecords.pop() + this.activeProfile = JSON.parse(last) - getHighlightedText: async function(textType){ - let titleCase = false - let highlightedText = window.getSelection ? window.getSelection().toString().trim() : '' - let el = false - if (highlightedText){ - const selection = window.getSelection(); - try { - el = selection.anchorNode[0] // this doesn't work in firefox - } catch(err){} - } - - if (el){ - let fieldGuid = el.getAttribute("data-guid") - let targetGuid = el.getAttribute("data-parent") - let pt = utilsProfile.returnPt(this.activeProfile, targetGuid) - let structure = this.returnStructureByGUID(targetGuid) - // make titlecase - if (textType == 'title'){ - titleCase = this.toTitleCase(highlightedText) - } else if (textType == 'lower'){ - titleCase = highlightedText.toLowerCase() - } + // save the profile to redo + if (this.redoRecords.length < this.undoRedoLimit) { + this.redoRecords.push(profile) + } else { // remove the oldest profile + this.redoRecords.shift() + this.redoRecords.push(profile) + } + // trigger xml refresh + this.dataChangedTimestamp = Date.now() - // {level: 0, propertyURI: structure.propertyURI}, - let pp - try { - pp = this.buildPropertyPath(structure, [], fieldGuid) - } catch(err){ - console.error("Error building PropertyPath: ", err) - return - } - let currentValue = this.returnLiteralValueFromProfile(targetGuid, pp) - - if (currentValue.length > 1){ - let newText = "" - let nonLatin = false - for (let val of currentValue){ - if (( val['@language'] && val['@language'].toLowerCase().includes('latn')) || val['@language'] == null){ - newText = val.value.replace(highlightedText, titleCase) - this.setValueLiteral(targetGuid, fieldGuid, pp, newText, val['@language'], false) - } else { - nonLatin = val + this.activeProfileSaved = false + if (usePreferenceStore().returnValue('--b-general-auto-save')) { + this.saveRecord() } - } + }, - // Adjust the non-Latin form to match - let otherScriptCodes = false - const config = useConfigStore() - for (let key in config.scriptShifterLangCodes){ - let codeObj = config.scriptShifterLangCodes[key] - if (nonLatin['@language'] && codeObj.code.toLowerCase() == nonLatin['@language'].toLowerCase()){ - otherScriptCodes = key - break + redoChange: async function () { + if (this.redoRecords.length < 1) { + alert("Nothing to redo. We can't go forward anymore.") + return } - } + // let profile = JSON.stringify(this.activeProfile) + this.currentState = JSON.stringify(this.activeProfile) - let transValue = await utilsNetwork.scriptShifterRequestTrans(otherScriptCodes, newText, false, "r2s") //abazin_cyrillic - this.setValueLiteral(targetGuid, nonLatin['@guid'], pp, transValue.output, nonLatin['@language'], false) - } else { - this.setValueLiteral(targetGuid, fieldGuid, pp, currentValue[0].value.replace(highlightedText, titleCase), currentValue[0]['@language'], false) - } + // save the profile to undo + if (this.undoRecords.length < this.undoRedoLimit) { + this.undoRecords.push(this.currentState) + } else { // remove the oldest profile + this.undoRecords.shift() + this.undoRecords.push(this.currentState) + } + // clear the cache + cachePt = {} + cacheGuid = {} + let last = this.redoRecords.pop() + this.activeProfile = JSON.parse(last) - } + // trigger xml refresh + this.dataChangedTimestamp = Date.now() + this.activeProfileSaved = false + if (usePreferenceStore().returnValue('--b-general-auto-save')) { + this.saveRecord() + } + }, - }, + getHighlightedText: async function (textType) { + let titleCase = false + let highlightedText = window.getSelection ? window.getSelection().toString().trim() : '' + let el = false + if (highlightedText) { + const selection = window.getSelection(); + try { + el = selection.anchorNode[0] // this doesn't work in firefox + } catch (err) { } + } + + if (el) { + let fieldGuid = el.getAttribute("data-guid") + let targetGuid = el.getAttribute("data-parent") + let pt = utilsProfile.returnPt(this.activeProfile, targetGuid) + let structure = this.returnStructureByGUID(targetGuid) + // make titlecase + if (textType == 'title') { + titleCase = this.toTitleCase(highlightedText) + } else if (textType == 'lower') { + titleCase = highlightedText.toLowerCase() + } + + // {level: 0, propertyURI: structure.propertyURI}, + let pp + try { + pp = this.buildPropertyPath(structure, [], fieldGuid) + } catch (err) { + console.error("Error building PropertyPath: ", err) + return + } + let currentValue = this.returnLiteralValueFromProfile(targetGuid, pp) + + if (currentValue.length > 1) { + let newText = "" + let nonLatin = false + for (let val of currentValue) { + if ((val['@language'] && val['@language'].toLowerCase().includes('latn')) || val['@language'] == null) { + newText = val.value.replace(highlightedText, titleCase) + this.setValueLiteral(targetGuid, fieldGuid, pp, newText, val['@language'], false) + } else { + nonLatin = val + } + } + + // Adjust the non-Latin form to match + let otherScriptCodes = false + const config = useConfigStore() + for (let key in config.scriptShifterLangCodes) { + let codeObj = config.scriptShifterLangCodes[key] + if (nonLatin['@language'] && codeObj.code.toLowerCase() == nonLatin['@language'].toLowerCase()) { + otherScriptCodes = key + break + } + } + + let transValue = await utilsNetwork.scriptShifterRequestTrans(otherScriptCodes, newText, false, "r2s") //abazin_cyrillic + this.setValueLiteral(targetGuid, nonLatin['@guid'], pp, transValue.output, nonLatin['@language'], false) + } else { + this.setValueLiteral(targetGuid, fieldGuid, pp, currentValue[0].value.replace(highlightedText, titleCase), currentValue[0]['@language'], false) + } - toTitleCase: function (str) { - return str.replace( - /\w\S*/g, - text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase() - ); - }, - /** - * Build the property path from the initial component GUID until we find the fieldGuid - * - * @param {Object} structure - Structure to build the propertyPath for - * @param {Object} pp - Starting propertyPath - * @param {String} endGuid - target at the end of path - */ - buildPropertyPath: function(structure, pp, endGuid){ - let userValue = structure.userValue - - // https://stackoverflow.com/questions/53543303/find-a-full-object-path-to-a-given-value-with-javascript - const traverse = (target, node) => { - for (let el in node){ - if (node[el] && typeof node[el] === "object"){ - let result = traverse(target, node[el]) - if (result){ - if (el.startsWith("http")){ - result.unshift(el) - } - return result - } - } else if (node['@guid'] && node['@guid'] == target){ - if (el.startsWith("http")){ - return [el] - } else { - return [] } - } - } - }; - let path = traverse(endGuid, userValue) - for (let p of path){ - pp.push( - { - level: pp.length, - propertyURI: p - } - ) - } + }, - return pp - }, + toTitleCase: function (str) { + return str.replace( + /\w\S*/g, + text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase() + ); + }, + + /** + * Build the property path from the initial component GUID until we find the fieldGuid + * + * @param {Object} structure - Structure to build the propertyPath for + * @param {Object} pp - Starting propertyPath + * @param {String} endGuid - target at the end of path + */ + buildPropertyPath: function (structure, pp, endGuid) { + let userValue = structure.userValue + + // https://stackoverflow.com/questions/53543303/find-a-full-object-path-to-a-given-value-with-javascript + const traverse = (target, node) => { + for (let el in node) { + if (node[el] && typeof node[el] === "object") { + let result = traverse(target, node[el]) + if (result) { + if (el.startsWith("http")) { + result.unshift(el) + } + return result + } + } else if (node['@guid'] && node['@guid'] == target) { + if (el.startsWith("http")) { + return [el] + } else { + return [] + } + } + } + + }; + let path = traverse(endGuid, userValue) + for (let p of path) { + pp.push( + { + level: pp.length, + propertyURI: p + } + ) + } + + return pp + }, @@ -8783,7 +8778,8 @@ export const useProfileStore = defineStore('profile', { - }, + + },