Skip to content

Commit

Permalink
Merge pull request #242 from UUDigitalHumanitieslab/feature/repeated-…
Browse files Browse the repository at this point in the history
…fields

Repeated fields
  • Loading branch information
jgonggrijp authored Dec 13, 2024
2 parents 4427cee + c6943d8 commit 2cb32a9
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 34 deletions.
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3.1"

services:
postgres:
image: postgres:14
Expand Down
46 changes: 41 additions & 5 deletions frontend/vre/field/field.model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _ from 'lodash';
import Backbone from 'backbone';
import {
canonicalSort,
Expand All @@ -21,7 +22,11 @@ export var Field = Backbone.Model.extend({
getMainDisplay() {
// Currently, only normalizedText is supported.
const value = this.get('value');
return value && value['edpoprec:originalText'];
if (!value) return value;
if (_.isArray(value)) {
return _.map(value, 'edpoprec:originalText').join(' ; ');
}
return value['edpoprec:originalText'];
},
getFieldInfo() {
const property = properties.get(this.id);
Expand All @@ -36,6 +41,18 @@ export var Field = Backbone.Model.extend({
},
});

/**
* Find the set of properties that apply to the given record.
* Mostly an implementation detail of {@link FlatFields} and
* {@link FlatterFields}.
*/
function selectProperties(record) {
return (
typeTranslation(record).isBibliographical ?
biblioProperties : bioProperties
);
}

/**
* This is an alternative, flat representation of the fields in a given
* option.record. Its purpose is to be easier to represent and manage from
Expand All @@ -61,14 +78,33 @@ export var FlatFields = Backbone.Collection.extend({
this.listenTo(this.record, 'change', _.flow([this.toFlat, this.set]));
},
toFlat: function(record) {
const properties = (
typeTranslation(record).isBibliographical ?
biblioProperties : bioProperties
);
const properties = selectProperties(record);
const fields = properties.map(prop => ({
key: prop.id,
value: record.get(prop.id),
}));
return fields;
},
});

/**
* Like {@link FlatFields}, but even flatter: if a field is repeated, every
* value is represented with a separate `{key, value}` pair.
* @class
*/
export var FlatterFields = FlatFields.extend({
modelId: function(fieldAttrs) {
const value = fieldAttrs.value;
const id = value && value['@id'];
return `${fieldAttrs.key} -- ${id}`;
},
toFlat: function(record) {
const properties = selectProperties(record);
return properties.reduce((fields, prop) => {
let value = record.get(prop.id);
if (!value) return fields.concat({key: prop.id, value: value});
if (!_.isArray(value)) value = [value];
return fields.concat(_.map(value, v => ({key: prop.id, value: v})));
}, []);
},
});
4 changes: 2 additions & 2 deletions frontend/vre/record/record.detail.view.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { vreChannel } from '../radio';
import { FlatAnnotations } from '../annotation/annotation.model';
import { RecordFieldsView } from '../field/record.fields.view';
import { RecordAnnotationsView } from '../field/record.annotations.view';
import { FlatFields } from '../field/field.model';
import { FlatterFields } from '../field/field.model';
import { VRECollectionView } from '../collection/collection.view';
import { typeTranslation } from '../utils/generic-functions.js';
import { GlobalVariables } from '../globals/variables';
Expand Down Expand Up @@ -44,7 +44,7 @@ export var RecordDetailView = CompositeView.extend({
initialize: function(options) {
var model = this.model;
this.fieldsView = new RecordFieldsView({
collection: new FlatFields(null, {record: model}),
collection: new FlatterFields(null, {record: model}),
}).render();
this.annotationsView = new RecordAnnotationsView({
collection: new FlatAnnotations(null, {record: model}),
Expand Down
38 changes: 21 additions & 17 deletions frontend/vre/utils/jsonld.model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _ from "lodash";
import {APICollection} from "./api.model";

/**
Expand Down Expand Up @@ -66,26 +67,29 @@ export var JsonLdCollection = APICollection.extend({
* The subject passed to this function as an argument is not changed.
* @param subjectsByID{Dictionary<JSONLDSubject>} - The full contents of the graph in JSON-LD
* @param subject{JSONLDSubject} - The subject including its predicates and objects to create a nested version of
* @param parentSubjectIDs{Array<String>} - For internal use of recursive function; leave at default value
* @returns {Object}
*/
export function nestSubject(subjectsByID, subject, parentSubjectIDs=[]) {
parentSubjectIDs.push(subject["@id"]);
const transformedSubject = _.clone(subject);
for (let property of Object.keys(subject)) {
if (subject[property].hasOwnProperty("@id")) {
// This is a reference to another subject
const refereedSubject = subjectsByID[subject[property]["@id"]];
if (refereedSubject && !(parentSubjectIDs.includes(refereedSubject["@id"]))) {
/* If the refereed subject was found in the graph, use it as replacement.
Only do this if we have not visited the same subject before,
to avoid an endless loop. (Alternative would be to create a circular reference) */
transformedSubject[property] = nestSubject(subjectsByID, refereedSubject, parentSubjectIDs);
}
}
export function nestSubject(subjectsByID, subject) {
const parentSubjectIDs = [];

function nest(subject) {
if (!_.has(subject, "@id")) return subject;
const id = subject["@id"];
const dereferenced = subjectsByID[id];
if (!dereferenced) return subject;
if (_.includes(parentSubjectIDs, id)) return subject;
parentSubjectIDs.push(id);
const transformedSubject = _.mapValues(dereferenced, nestProperty);
parentSubjectIDs.pop();
return transformedSubject;
}
parentSubjectIDs.pop();
return transformedSubject;

function nestProperty(value) {
if (_.isArray(value)) return _.map(value, nest);
return nest(value);
}

return nest(subject);
}

/**
Expand Down
35 changes: 27 additions & 8 deletions frontend/vre/utils/jsonld.model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,45 +66,64 @@ const exampleJsonLDGraph = [{
"owl:sameAs": {
"@id": "http://example.com/descForS7",
},
}, {
"@id": "http://example.com/s8",
"dc:title": "Title with nested array",
"dc:description": [{
"@id": "http://example.com/descForS2",
}, {
"@id": "http://example.com/descForS4",
}, {
"@id": "http://example.com/descForS7",
}],
}];

describe('nestSubject', () => {
const subjectsByID = _.keyBy(exampleJsonLDGraph, "@id");

it('does not change anything if there are no references', () => {
const subjectsByID = _.keyBy(exampleJsonLDGraph, "@id");
const subject = subjectsByID["http://example.com/s1"];
const ns = nestSubject(subjectsByID, subject);
assert.equal(ns["dc:title"], "Title without references");
});

it('correctly handles internal references', () => {
const subjectsByID = _.keyBy(exampleJsonLDGraph, "@id");
const subject = subjectsByID["http://example.com/s2"];
const ns = nestSubject(subjectsByID, subject);
assert.equal("Random description", ns["dc:description"]["example:value"]);
});

it('does not alter external references', () => {
const subjectsByID = _.keyBy(exampleJsonLDGraph, "@id");
const subject = subjectsByID["http://example.com/s3"];
const ns = nestSubject(subjectsByID, subject);
assert.equal(subject["dc:description"], ns["dc:description"]);
});

it('correctly handles a nested internal reference', () => {
const subjectsByID = _.keyBy(exampleJsonLDGraph, "@id");
const subject = subjectsByID["http://example.com/s4"];
const ns = nestSubject(subjectsByID, subject);
assert.equal("Random description", ns["dc:description"]["example:value"]["example:value"]);
});
it('does not resolve a recursive reference', () => {
const subjectsByID = _.keyBy(exampleJsonLDGraph, "@id");

it('does not resolve a cyclical reference', () => {
const subject = subjectsByID["http://example.com/s5"];
const ns = nestSubject(subjectsByID, subject);
assert.equal("http://example.com/s5", ns["owl:sameAs"]["owl:sameAs"]["@id"]);
});

it('detects cycles even if the root subject is not involved', () => {
const subjectsByID = _.keyBy(exampleJsonLDGraph, "@id");
const subject = subjectsByID["http://example.com/s7"];
const ns = nestSubject(subjectsByID, subject);
assert.equal("http://example.com/descForS7", ns["dc:description"]["owl:sameAs"]["owl:sameAs"]["@id"]);
});

it('can handle arrays of internal references', () => {
const subject = subjectsByID["http://example.com/s8"];
const ns = nestSubject(subjectsByID, subject);
assert.equal("Random description", ns["dc:description"][0]["example:value"]);
assert.equal("Random description", ns["dc:description"][1]["example:value"]["example:value"]);
assert.equal("http://example.com/descForS7", ns["dc:description"][2]["owl:sameAs"]["owl:sameAs"]["@id"]);
});
});

describe('getStringLiteral', () => {
Expand Down Expand Up @@ -135,4 +154,4 @@ describe('getStringLiteral', () => {
"@value": "bonjour",
}]) === "hello");
});
});
});

0 comments on commit 2cb32a9

Please sign in to comment.