DS-4107 Retrieve and model metadata as a map

This commit is contained in:
Chris Wilper
2018-12-22 23:03:45 -05:00
parent 34e78bd495
commit 2368df9513
35 changed files with 310 additions and 281 deletions

View File

@@ -7,10 +7,12 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let metadatum of metadata" class="metadata-row">
<td>{{metadatum.key}}</td>
<td>{{metadatum.value}}</td>
<td>{{metadatum.language}}</td>
<ng-container *ngFor="let entry of metadata | keyvalue">
<tr *ngFor="let value of entry.value" class="metadata-row">
<td>{{entry.key}}</td>
<td>{{value.value}}</td>
<td>{{value.language}}</td>
</tr>
</ng-container>
</tbody>
</table>

View File

@@ -1,6 +1,6 @@
import {Component, Input, OnInit} from '@angular/core';
import {Item} from '../../../core/shared/item.model';
import {Metadatum} from '../../../core/shared/metadatum.model';
import {MetadataMap} from '../../../core/shared/metadata.interfaces';
@Component({
selector: 'ds-modify-item-overview',
@@ -12,7 +12,7 @@ import {Metadatum} from '../../../core/shared/metadatum.model';
export class ModifyItemOverviewComponent implements OnInit {
@Input() item: Item;
metadata: Metadatum[];
metadata: MetadataMap;
ngOnInit(): void {
this.metadata = this.item.metadata;

View File

@@ -1,5 +1,5 @@
<ds-metadata-field-wrapper [label]="label | translate">
<a *ngFor="let metadatum of values; let last=last;" [href]="metadatum.value">
{{ linktext || metadatum.value }}<span *ngIf="!last" [innerHTML]="separator"></span>
<a *ngFor="let value of values; let last=last;" [href]="value.value">
{{ linktext || value.value }}<span *ngIf="!last" [innerHTML]="separator"></span>
</a>
</ds-metadata-field-wrapper>

View File

@@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core';
import { MetadataValuesComponent } from '../metadata-values/metadata-values.component';
import { MetadataValue } from '../../../core/shared/metadata.interfaces';
/**
* This component renders the configured 'values' into the ds-metadata-field-wrapper component as a link.
@@ -18,7 +19,7 @@ export class MetadataUriValuesComponent extends MetadataValuesComponent {
@Input() linktext: any;
@Input() values: any;
@Input() values: MetadataValue[];
@Input() separator: string;

View File

@@ -1,5 +1,5 @@
<ds-metadata-field-wrapper [label]="label | translate">
<span *ngFor="let metadatum of values; let last=last;">
{{metadatum.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
<span *ngFor="let value of values; let last=last;">
{{value.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
</span>
</ds-metadata-field-wrapper>

View File

@@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core';
import { Metadatum } from '../../../core/shared/metadatum.model';
import { MetadataValue } from '../../../core/shared/metadata.interfaces';
/**
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
@@ -12,7 +12,7 @@ import { Metadatum } from '../../../core/shared/metadatum.model';
})
export class MetadataValuesComponent {
@Input() values: Metadatum[];
@Input() values: MetadataValue[];
@Input() separator: string;

View File

@@ -17,7 +17,7 @@
<dt class="col-md-4">{{"item.page.filesection.description" | translate}}</dt>
<dd class="col-md-8">{{file.findMetadata("dc.description")}}</dd>
<dd class="col-md-8">{{file.firstMetadataValue("dc.description")}}</dd>
</dl>
</div>
<div class="col-2">

View File

@@ -9,11 +9,13 @@
</div>
<table class="table table-responsive table-striped">
<tbody>
<tr *ngFor="let metadatum of (metadata$ | async)">
<td>{{metadatum.key}}</td>
<td>{{metadatum.value}}</td>
<td>{{metadatum.language}}</td>
<ng-container *ngFor="let entry of (metadata$ | async) | keyvalue">
<tr *ngFor="let value of entry.value">
<td>{{entry.key}}</td>
<td>{{value.value}}</td>
<td>{{value.language}}</td>
</tr>
</ng-container>
</tbody>
</table>
<ds-item-page-full-file-section [item]="item"></ds-item-page-full-file-section>

View File

@@ -6,7 +6,7 @@ import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { ItemPageComponent } from '../simple/item-page.component';
import { Metadatum } from '../../core/shared/metadatum.model';
import { MetadataMap } from '../../core/shared/metadata.interfaces';
import { ItemDataService } from '../../core/data/item-data.service';
import { RemoteData } from '../../core/data/remote-data';
@@ -34,7 +34,7 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
itemRD$: Observable<RemoteData<Item>>;
metadata$: Observable<Metadatum[]>;
metadata$: Observable<MetadataMap>;
constructor(route: ActivatedRoute, items: ItemDataService, metadataService: MetadataService) {
super(route, items, metadataService);

View File

@@ -1,3 +1,3 @@
<div class="item-page-specific-field">
<ds-metadata-values [values]="item?.filterMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-values>
<ds-metadata-values [values]="item?.allMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-values>
</div>

View File

@@ -1,3 +1,3 @@
<h2 class="item-page-title-field">
<ds-metadata-values [values]="item?.filterMetadata(fields)"></ds-metadata-values>
<ds-metadata-values [values]="item?.allMetadata(fields)"></ds-metadata-values>
</h2>

View File

@@ -1,3 +1,3 @@
<div class="item-page-specific-field">
<ds-metadata-uri-values [values]="item?.filterMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-uri-values>
<ds-metadata-uri-values [values]="item?.allMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-uri-values>
</div>

View File

@@ -1,5 +1,5 @@
import { autoserialize } from 'cerialize';
import { Metadatum } from '../core/shared/metadatum.model';
import { MetadataMap } from '../core/shared/metadata.interfaces';
import { ListableObject } from '../shared/object-collection/shared/listable-object.model';
/**
@@ -16,6 +16,6 @@ export class NormalizedSearchResult implements ListableObject {
* The metadata that was used to find this item, hithighlighted
*/
@autoserialize
hitHighlights: Metadatum[];
hitHighlights: MetadataMap;
}

View File

@@ -1,5 +1,5 @@
import { DSpaceObject } from '../core/shared/dspace-object.model';
import { Metadatum } from '../core/shared/metadatum.model';
import { MetadataMap } from '../core/shared/metadata.interfaces';
import { ListableObject } from '../shared/object-collection/shared/listable-object.model';
/**
@@ -14,6 +14,6 @@ export class SearchResult<T extends DSpaceObject> implements ListableObject {
/**
* The metadata that was used to find this item, hithighlighted
*/
hitHighlights: Metadatum[];
hitHighlights: MetadataMap;
}

View File

@@ -38,8 +38,8 @@ import { DSpaceObject } from '../shared/dspace-object.model';
export class BrowseService {
protected linkPath = 'browses';
private static toSearchKeyArray(metadatumKey: string): string[] {
const keyParts = metadatumKey.split('.');
private static toSearchKeyArray(metadataKey: string): string[] {
const keyParts = metadataKey.split('.');
const searchFor = [];
searchFor.push('*');
for (let i = 0; i < keyParts.length - 1; i++) {
@@ -47,7 +47,7 @@ export class BrowseService {
const nextPart = [...prevParts, '*'].join('.');
searchFor.push(nextPart);
}
searchFor.push(metadatumKey);
searchFor.push(metadataKey);
return searchFor;
}
@@ -179,8 +179,8 @@ export class BrowseService {
return this.rdb.toRemoteDataObservable(requestEntry$, payload$);
}
getBrowseURLFor(metadatumKey: string, linkPath: string): Observable<string> {
const searchKeyArray = BrowseService.toSearchKeyArray(metadatumKey);
getBrowseURLFor(metadataKey: string, linkPath: string): Observable<string> {
const searchKeyArray = BrowseService.toSearchKeyArray(metadataKey);
return this.getBrowseDefinitions().pipe(
getRemoteDataPayload(),
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
@@ -191,7 +191,7 @@ export class BrowseService {
),
map((def: BrowseDefinition) => {
if (isEmpty(def) || isEmpty(def._links) || isEmpty(def._links[linkPath])) {
throw new Error(`A browse endpoint for ${linkPath} on ${metadatumKey} isn't configured`);
throw new Error(`A browse endpoint for ${linkPath} on ${metadataKey} isn't configured`);
} else {
return def._links[linkPath];
}

View File

@@ -1,7 +1,6 @@
import { autoserialize, autoserializeAs, deserialize, serialize } from 'cerialize';
import { DSpaceObject } from '../../shared/dspace-object.model';
import { Metadatum } from '../../shared/metadatum.model';
import { MetadataMap } from '../../shared/metadata.interfaces';
import { ResourceType } from '../../shared/resource-type';
import { mapsTo } from '../builders/build-decorators';
import { NormalizedObject } from './normalized-object.model';
@@ -46,10 +45,10 @@ export class NormalizedDSpaceObject extends NormalizedObject {
type: ResourceType;
/**
* An array containing all metadata of this DSpaceObject
* All metadata of this DSpaceObject
*/
@autoserializeAs(Metadatum)
metadata: Metadatum[];
@autoserialize
metadata: MetadataMap;
/**
* An array of DSpaceObjects that are direct parents of this DSpaceObject

View File

@@ -7,7 +7,7 @@ import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { hasValue } from '../../shared/empty.util';
import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model';
import { Metadatum } from '../shared/metadatum.model';
import { MetadataMap, MetadataValue } from '../shared/metadata.interfaces';
@Injectable()
export class SearchResponseParsingService implements ResponseParsingService {
@@ -16,17 +16,17 @@ export class SearchResponseParsingService implements ResponseParsingService {
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const payload = data.payload._embedded.searchResult;
const hitHighlights = payload._embedded.objects
const hitHighlights: MetadataMap[] = payload._embedded.objects
.map((object) => object.hitHighlights)
.map((hhObject) => {
const mdMap: MetadataMap = {};
if (hhObject) {
return Object.keys(hhObject).map((key) => Object.assign(new Metadatum(), {
key: key,
value: hhObject[key].join('...')
}))
} else {
return [];
for (const key of Object.keys(hhObject)) {
const value: MetadataValue = { value: hhObject[key].join('...'), language: null };
mdMap[key] = [ value ];
}
}
return mdMap;
});
const dsoSelfLinks = payload._embedded.objects

View File

@@ -91,9 +91,9 @@ export class DSpaceRESTv2Service {
const form: FormData = new FormData();
form.append('name', dso.name);
if (dso.metadata) {
for (const i of Object.keys(dso.metadata)) {
if (isNotEmpty(dso.metadata[i].value)) {
form.append(dso.metadata[i].key, dso.metadata[i].value);
for (const key of Object.keys(dso.metadata)) {
for (const value of dso.allMetadataValues(key)) {
form.append(key, value);
}
}
}

View File

@@ -294,6 +294,10 @@ export class MetadataService {
}
}
private hasType(value: string): boolean {
return this.currentObject.value.hasMetadata('dc.type', { value: value, ignoreCase: true });
}
/**
* Returns true if this._item is a dissertation
*
@@ -301,14 +305,7 @@ export class MetadataService {
* true if this._item has a dc.type equal to 'Thesis'
*/
private isDissertation(): boolean {
let isDissertation = false;
for (const metadatum of this.currentObject.value.metadata) {
if (metadatum.key === 'dc.type') {
isDissertation = metadatum.value.toLowerCase() === 'thesis';
break;
}
}
return isDissertation;
return this.hasType('thesis');
}
/**
@@ -318,40 +315,15 @@ export class MetadataService {
* true if this._item has a dc.type equal to 'Technical Report'
*/
private isTechReport(): boolean {
let isTechReport = false;
for (const metadatum of this.currentObject.value.metadata) {
if (metadatum.key === 'dc.type') {
isTechReport = metadatum.value.toLowerCase() === 'technical report';
break;
}
}
return isTechReport;
return this.hasType('technical report');
}
private getMetaTagValue(key: string): string {
let value: string;
for (const metadatum of this.currentObject.value.metadata) {
if (metadatum.key === key) {
value = metadatum.value;
}
}
return value;
return this.currentObject.value.firstMetadataValue(key);
}
private getFirstMetaTagValue(keys: string[]): string {
let value: string;
for (const metadatum of this.currentObject.value.metadata) {
for (const key of keys) {
if (key === metadatum.key) {
value = metadatum.value;
break;
}
}
if (value !== undefined) {
break;
}
}
return value;
return this.currentObject.value.firstMetadataValue(keys);
}
private getMetaTagValuesAndCombine(key: string): string {
@@ -359,15 +331,7 @@ export class MetadataService {
}
private getMetaTagValues(keys: string[]): string[] {
const values: string[] = [];
for (const metadatum of this.currentObject.value.metadata) {
for (const key of keys) {
if (key === metadatum.key) {
values.push(metadatum.value);
}
}
}
return values;
return this.currentObject.value.allMetadataValues(keys);
}
private addMetaTag(property: string, content: string): void {

View File

@@ -16,7 +16,7 @@ export class Collection extends DSpaceObject {
* Corresponds to the metadata field dc.description
*/
get introductoryText(): string {
return this.findMetadata('dc.description');
return this.firstMetadataValue('dc.description');
}
/**
@@ -24,7 +24,7 @@ export class Collection extends DSpaceObject {
* Corresponds to the metadata field dc.description.abstract
*/
get shortDescription(): string {
return this.findMetadata('dc.description.abstract');
return this.firstMetadataValue('dc.description.abstract');
}
/**
@@ -32,7 +32,7 @@ export class Collection extends DSpaceObject {
* Corresponds to the metadata field dc.rights
*/
get copyrightText(): string {
return this.findMetadata('dc.rights');
return this.firstMetadataValue('dc.rights');
}
/**
@@ -40,7 +40,7 @@ export class Collection extends DSpaceObject {
* Corresponds to the metadata field dc.rights.license
*/
get license(): string {
return this.findMetadata('dc.rights.license');
return this.firstMetadataValue('dc.rights.license');
}
/**
@@ -48,7 +48,7 @@ export class Collection extends DSpaceObject {
* Corresponds to the metadata field dc.description.tableofcontents
*/
get sidebarText(): string {
return this.findMetadata('dc.description.tableofcontents');
return this.firstMetadataValue('dc.description.tableofcontents');
}
/**

View File

@@ -17,7 +17,7 @@ export class Community extends DSpaceObject {
* Corresponds to the metadata field dc.description
*/
get introductoryText(): string {
return this.findMetadata('dc.description');
return this.firstMetadataValue('dc.description');
}
/**
@@ -25,7 +25,7 @@ export class Community extends DSpaceObject {
* Corresponds to the metadata field dc.description.abstract
*/
get shortDescription(): string {
return this.findMetadata('dc.description.abstract');
return this.firstMetadataValue('dc.description.abstract');
}
/**
@@ -33,7 +33,7 @@ export class Community extends DSpaceObject {
* Corresponds to the metadata field dc.rights
*/
get copyrightText(): string {
return this.findMetadata('dc.rights');
return this.firstMetadataValue('dc.rights');
}
/**
@@ -41,7 +41,7 @@ export class Community extends DSpaceObject {
* Corresponds to the metadata field dc.description.tableofcontents
*/
get sidebarText(): string {
return this.findMetadata('dc.description.tableofcontents');
return this.firstMetadataValue('dc.description.tableofcontents');
}
/**

View File

@@ -1,5 +1,5 @@
import { Metadatum } from './metadatum.model'
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { MetadataMap, MetadataValue, MetadataValueFilter } from './metadata.interfaces';
import { Metadata } from './metadata.model';
import { CacheableObject } from '../cache/object-cache.reducer';
import { RemoteData } from '../data/remote-data';
import { ResourceType } from './resource-type';
@@ -35,14 +35,14 @@ export class DSpaceObject implements CacheableObject, ListableObject {
* The name for this DSpaceObject
*/
get name(): string {
return this.findMetadata('dc.title');
return this.firstMetadataValue('dc.title');
}
/**
* An array containing all metadata of this DSpaceObject
* All metadata of this DSpaceObject
*/
@autoserialize
metadata: Metadatum[] = [];
metadata: MetadataMap;
/**
* An array of DSpaceObjects that are direct parents of this DSpaceObject
@@ -54,42 +54,29 @@ export class DSpaceObject implements CacheableObject, ListableObject {
*/
owner: Observable<RemoteData<DSpaceObject>>;
/**
* Find a metadata field by key and language
*
* This method returns the value of the first element
* in the metadata array that matches the provided
* key and language
*
* @param key
* @param language
* @return string
*/
findMetadata(key: string, language?: string): string {
const metadatum = this.metadata.find((m: Metadatum) => {
return m.key === key && (isEmpty(language) || m.language === language)
});
if (isNotEmpty(metadatum)) {
return metadatum.value;
} else {
return undefined;
}
/** Gets all matching metadata in this DSpaceObject. See `Metadata.all` for more information. */
allMetadata(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): MetadataValue[] {
return Metadata.all(this.metadata, keyOrKeys, valueFilter);
}
/**
* Find metadata by an array of keys
*
* This method returns the values of the element
* in the metadata array that match the provided
* key(s)
*
* @param key(s)
* @return Array<Metadatum>
*/
filterMetadata(keys: string[]): Metadatum[] {
return this.metadata.filter((metadatum: Metadatum) => {
return keys.some((key) => key === metadatum.key);
});
/** Like `allMetadata`, but only returns string values. */
allMetadataValues(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string[] {
return Metadata.allValues(this.metadata, keyOrKeys, valueFilter);
}
/** Gets the first matching metadata in this DSpaceObject, or `undefined`. */
firstMetadata(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): MetadataValue {
return Metadata.first(this.metadata, keyOrKeys, valueFilter);
}
/** Like `firstMetadata`, but only returns a string value, or `undefined`. */
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
return Metadata.firstValue(this.metadata, keyOrKeys, valueFilter);
}
/** Checks for matching metadata in this DSpaceObject. */
hasMetadata(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): boolean {
return Metadata.has(this.metadata, keyOrKeys, valueFilter);
}
}

View File

@@ -0,0 +1,30 @@
/** A map of metadata keys to an ordered list of MetadataValue objects. */
export interface MetadataMap {
[ key: string ]: MetadataValue[];
}
/** A single metadata value and its properties. */
export interface MetadataValue {
/** The language. */
language: string;
/** The string value. */
value: string;
}
/** Constraints for matching metadata values. */
export interface MetadataValueFilter {
/** The language constraint. */
language?: string;
/** The value constraint. */
value?: string;
/** Whether the value constraint should match without regard to case. */
ignoreCase?: boolean;
/** Whether the value constraint should match as a substring. */
substring?: boolean;
}

View File

@@ -0,0 +1,119 @@
import { isEmpty } from '../../shared/empty.util';
import { MetadataMap, MetadataValue, MetadataValueFilter } from './metadata.interfaces';
/**
* Static utility methods for working with DSpace metadata.
*/
export class Metadata {
/**
* Gets all matching metadata.
*
* @param {MetadataMap|MetadataMap[]} mapOrMaps The source map(s). Values will only be returned from one map --
* the first with at least one match.
* @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported, so `'*'` will
* match all keys, `'dc.date.*'` will match all qualified dc dates, and so on. Exact keys will be evaluated
* (and matches returned) in the order they are given in the parameter. When multiple keys match a wildcard,
* they are evaluated in the order they are stored in the map (alphanumeric if obtained from the REST api).
* If duplicate or overlapping keys are specified, the first one takes precedence. For example, specifying
* `['dc.date', 'dc.*', '*']` will cause any `dc.date` values to be evaluated (and returned, if matched)
* first, followed by any other `dc` metadata values, followed by any other (non-dc) metadata values.
* @param {MetadataValueFilter} filter The value filter to use.
* @returns {MetadataValue[]} the matching values or an empty array.
*/
public static all(mapOrMaps: MetadataMap | MetadataMap[], keyOrKeys: string | string[],
filter?: MetadataValueFilter): MetadataValue[] {
const mdMaps: MetadataMap[] = mapOrMaps instanceof Array ? mapOrMaps : [ mapOrMaps ];
const matches: MetadataValue[] = [];
for (const mdMap of mdMaps) {
for (const mdKey of Metadata.resolveKeys(mdMap, keyOrKeys)) {
const candidates = mdMap[mdKey];
if (candidates) {
for (const candidate of candidates) {
if (Metadata.valueMatches(candidate, filter)) {
matches.push(candidate);
}
}
}
}
if (!isEmpty(matches)) {
return matches;
}
}
return matches;
}
/** Like `all`, but only returns string values. */
public static allValues(mapOrMaps: MetadataMap | MetadataMap[], keyOrKeys: string | string[],
filter?: MetadataValueFilter): string[] {
return Metadata.all(mapOrMaps, keyOrKeys, filter).map((mdValue) => mdValue.value);
}
/** Gets the first matching MetadataValue object in a map, or `undefined`. */
public static first(mdMapOrMaps: MetadataMap | MetadataMap[], keyOrKeys: string | string[],
filter?: MetadataValueFilter): MetadataValue {
const mdMaps: MetadataMap[] = mdMapOrMaps instanceof Array ? mdMapOrMaps : [ mdMapOrMaps ];
for (const mdMap of mdMaps) {
for (const key of Metadata.resolveKeys(mdMap, keyOrKeys)) {
const values: MetadataValue[] = mdMap[key];
if (values) {
return values.find((value: MetadataValue) => Metadata.valueMatches(value, filter));
}
}
}
}
/** Like `first`, but only returns a string value, or `undefined`. */
public static firstValue(mdMapOrMaps: MetadataMap | MetadataMap[], keyOrKeys: string | string[],
filter?: MetadataValueFilter): string {
const value = Metadata.first(mdMapOrMaps, keyOrKeys, filter);
return value === undefined ? undefined : value.value;
}
/** Checks the given map for a matching value. */
public static has(mdMapOrMaps: MetadataMap | MetadataMap[], keyOrKeys: string | string[],
filter?: MetadataValueFilter): boolean {
return Metadata.first(mdMapOrMaps, keyOrKeys, filter) !== undefined;
}
/** Checks if a value matches a filter. */
public static valueMatches(mdValue: MetadataValue, filter: MetadataValueFilter) {
if (!filter) {
return true;
} else if (filter.language && filter.language !== mdValue.language) {
return false;
} else if (filter.value) {
let fValue = filter.value;
let mValue = mdValue.value;
if (filter.ignoreCase) {
fValue = filter.value.toLowerCase();
mValue = mdValue.value.toLowerCase();
}
if (filter.substring) {
return mValue.includes(fValue);
} else {
return mValue === fValue;
}
}
return true;
}
/** Gets the list of keys in the map limited by, and in the order given by `keyOrKeys` */
private static resolveKeys(mdMap: MetadataMap, keyOrKeys: string | string[]): string[] {
const inputKeys: string[] = keyOrKeys instanceof Array ? keyOrKeys : [ keyOrKeys ];
const outputKeys: string[] = [];
for (const inputKey of inputKeys) {
if (inputKey.includes('*')) {
const inputKeyRegex = new RegExp('^' + inputKey.replace('.', '\.').replace('*', '.*') + '$');
for (const mapKey of Object.keys(mdMap)) {
if (!outputKeys.includes(mapKey) && inputKeyRegex.test(mapKey)) {
outputKeys.push(mapKey);
}
}
} else if (mdMap.hasOwnProperty(inputKey) && !outputKeys.includes(inputKey)) {
outputKeys.push(inputKey);
}
}
return outputKeys;
}
}

View File

@@ -1,23 +0,0 @@
import { autoserialize } from 'cerialize';
export class Metadatum {
/**
* The metadata field of this Metadatum
*/
@autoserialize
key: string;
/**
* The language of this Metadatum
*/
@autoserialize
language: string;
/**
* The value of this Metadatum
*/
@autoserialize
value: string;
}

View File

@@ -8,6 +8,7 @@ import { FormGroup } from '@angular/forms';
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
import { TranslateService } from '@ngx-translate/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.interfaces';
import { isNotEmpty } from '../../empty.util';
import { ResourceType } from '../../../core/shared/resource-type';
@@ -64,7 +65,7 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit {
ngOnInit(): void {
this.formModel.forEach(
(fieldModel: DynamicInputModel) => {
fieldModel.value = this.dso.findMetadata(fieldModel.name);
fieldModel.value = this.dso.firstMetadataValue(fieldModel.name);
}
);
this.formGroup = this.formService.createFormGroup(this.formModel);
@@ -77,20 +78,24 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit {
}
/**
* Checks which new fields where added and sends the updated version of the DSO to the parent component
* Checks which new fields were added and sends the updated version of the DSO to the parent component
*/
onSubmit() {
const metadata = this.formModel.map(
(fieldModel: DynamicInputModel) => {
return { key: fieldModel.name, value: fieldModel.value }
const formMetadata = new Object() as MetadataMap;
this.formModel.forEach((fieldModel: DynamicInputModel) => {
const value: MetadataValue = { value: fieldModel.value as string, language: null };
if (formMetadata.hasOwnProperty(fieldModel.name)) {
formMetadata[fieldModel.name].push(value);
} else {
formMetadata[fieldModel.name] = [ value ];
}
);
const filteredOldMetadata = this.dso.metadata.filter((filter) => !metadata.map((md) => md.key).includes(filter.key));
const filteredNewMetadata = metadata.filter((md) => isNotEmpty(md.value));
});
const newMetadata = [...filteredOldMetadata, ...filteredNewMetadata];
const updatedDSO = Object.assign({}, this.dso, {
metadata: newMetadata,
metadata: {
...this.dso.metadata,
...formMetadata
},
type: ResourceType.Community
});
this.submitForm.emit(updatedDSO);

View File

@@ -1,6 +1,5 @@
import { Component, Inject } from '@angular/core';
import { ListableObject } from '../listable-object.model';
import { hasValue } from '../../../empty.util';
@Component({
selector: 'ds-abstract-object-element',
@@ -11,8 +10,4 @@ export class AbstractListableElementComponent <T extends ListableObject> {
public constructor(@Inject('objectElementProvider') public listableObject: ListableObject) {
this.object = listableObject as T;
}
hasValue(data) {
return hasValue(data);
}
}

View File

@@ -5,19 +5,19 @@
</ds-grid-thumbnail>
</a>
<div class="card-body">
<h4 class="card-title">{{object.findMetadata('dc.title')}}</h4>
<h4 class="card-title">{{object.firstMetadataValue('dc.title')}}</h4>
<ds-truncatable-part [id]="object.id" [minLines]="2">
<p *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" class="item-authors card-text text-muted">
<span *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
<p *ngIf="object.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])" class="item-authors card-text text-muted">
<span *ngFor="let author of object.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{author}}
<span *ngIf="!last">; </span>
</span>
<span *ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-date">{{object.findMetadata("dc.date.issued")}}</span>
<span *ngIf="object.hasMetadata('dc.date.issued')" class="item-date">{{object.firstMetadataValue("dc.date.issued")}}</span>
</p>
</ds-truncatable-part>
<ds-truncatable-part [id]="object.id" [minLines]="5">
<p *ngIf="object.findMetadata('dc.description.abstract')" class="item-abstract card-text">{{object.findMetadata("dc.description.abstract") }}</p>
<p *ngIf="object.hasMetadata('dc.description.abstract')" class="item-abstract card-text">{{object.firstMetadataValue("dc.description.abstract")}}</p>
</ds-truncatable-part>
<div class="text-center pt-2">

View File

@@ -8,20 +8,20 @@
</a>
<div class="card-body">
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.findMetadata('dc.title')"></h4>
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
<p *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
class="item-authors card-text text-muted">
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="1">
<span *ngIf="hasValue(dso.findMetadata('dc.date.issued'))" class="item-date">{{dso.findMetadata("dc.date.issued")}}</span>
<span *ngFor="let authorMd of dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);">,
<span [innerHTML]="authorMd.value"></span>
<span *ngIf="dso.hasMetadata('dc.date.issued')" class="item-date">{{dso.firstMetadataValue('dc.date.issued')}}</span>
<span *ngFor="let author of dso.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);">,
<span [innerHTML]="author"></span>
</span>
</ds-truncatable-part>
</p>
<p class="item-abstract card-text">
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3">
<span [innerHTML]="getFirstValue('dc.description.abstract')"></span>
<span [innerHTML]="firstMetadataValue('dc.description.abstract')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">

View File

@@ -2,12 +2,11 @@ import { Component, Inject } from '@angular/core';
import { SearchResult } from '../../../+search-page/search-result.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { Metadatum } from '../../../core/shared/metadatum.model';
import { isEmpty, hasNoValue, hasValue } from '../../empty.util';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
import { TruncatableService } from '../../truncatable/truncatable.service';
import { Observable } from 'rxjs';
import { Metadata } from '../../../core/shared/metadata.model';
@Component({
selector: 'ds-search-result-grid-element',
@@ -22,39 +21,14 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
this.dso = this.object.dspaceObject;
}
getValues(keys: string[]): string[] {
const results: string[] = new Array<string>();
this.object.hitHighlights.forEach(
(md: Metadatum) => {
if (keys.indexOf(md.key) > -1) {
results.push(md.value);
}
}
);
if (isEmpty(results)) {
this.dso.filterMetadata(keys).forEach(
(md: Metadatum) => {
results.push(md.value);
}
);
}
return results;
/** Gets all matching metadata string values from hitHighlights or dso metadata, preferring hitHighlights. */
allMetadataValues(keyOrKeys: string | string[]): string[] {
return Metadata.allValues([this.object.hitHighlights, this.dso.metadata], keyOrKeys);
}
getFirstValue(key: string): string {
let result: string;
this.object.hitHighlights.some(
(md: Metadatum) => {
if (key === md.key) {
result = md.value;
return true;
}
}
);
if (hasNoValue(result)) {
result = this.dso.findMetadata(key);
}
return result;
/** Gets the first matching metadata string value from hitHighlights or dso metadata, preferring hitHighlights. */
firstMetadataValue(keyOrKeys: string | string[]): string {
return Metadata.firstValue([this.object.hitHighlights, this.dso.metadata], keyOrKeys);
}
isCollapsed(): Observable<boolean> {

View File

@@ -1,23 +1,23 @@
<ds-truncatable [id]="object.id">
<a [routerLink]="['/items/' + object.id]" class="lead">
{{object.findMetadata("dc.title")}}
{{object.firstMetadataValue("dc.title")}}
</a>
<div>
<ds-truncatable-part [id]="object.id" [minLines]="1">
<span class="text-muted">
<span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
<span *ngIf="object.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
class="item-list-authors">
<span *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
<span *ngFor="let author of object.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{author}}
<span *ngIf="!last">; </span>
</span>
</span>
(<span *ngIf="hasValue(object.findMetadata('dc.publisher'))" class="item-list-publisher">{{object.findMetadata("dc.publisher")}}, </span><span
*ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-list-date">{{object.findMetadata("dc.date.issued")}}</span>)
(<span *ngIf="object.hasMetadata('dc.publisher')" class="item-list-publisher">{{object.firstMetadataValue("dc.publisher")}}, </span><span
*ngIf="object.hasMetadata('dc.date.issued')" class="item-list-date">{{object.firstMetadataValue("dc.date.issued")}}</span>)
</span>
</ds-truncatable-part>
<ds-truncatable-part [id]="object.id" [minLines]="3">
<div *ngIf="object.findMetadata('dc.description.abstract')" class="item-list-abstract">
{{object.findMetadata("dc.description.abstract")}}
<div *ngIf="object.hasMetadata('dc.description.abstract')" class="item-list-abstract">
{{object.firstMetadataValue("dc.description.abstract")}}
</div>
</ds-truncatable-part>
</div>

View File

@@ -1,2 +1,2 @@
<a [routerLink]="['/collections/' + dso.id]" class="lead" [innerHTML]="getFirstValue('dc.title')"></a>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="getFirstValue('dc.description.abstract')"></div>
<a [routerLink]="['/collections/' + dso.id]" class="lead" [innerHTML]="firstMetadataValue('dc.title')"></a>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div>

View File

@@ -1,2 +1,2 @@
<a [routerLink]="['/communities/' + dso.id]" class="lead" [innerHTML]="getFirstValue('dc.title')"></a>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="getFirstValue('dc.description.abstract')"></div>
<a [routerLink]="['/communities/' + dso.id]" class="lead" [innerHTML]="firstMetadataValue('dc.title')"></a>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div>

View File

@@ -1,24 +1,24 @@
<ds-truncatable [id]="dso.id">
<a
[routerLink]="['/items/' + dso.id]" class="lead"
[innerHTML]="getFirstValue('dc.title')"></a>
[innerHTML]="firstMetadataValue('dc.title')"></a>
<span class="text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
(<span *ngIf="dso.findMetadata('dc.publisher')" class="item-list-publisher"
[innerHTML]="getFirstValue('dc.publisher') + ', '"></span><span
*ngIf="hasValue(dso.findMetadata('dc.date.issued'))" class="item-list-date"
[innerHTML]="getFirstValue('dc.date.issued')"></span>)
<span *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
(<span *ngIf="dso.hasMetadata('dc.publisher')" class="item-list-publisher"
[innerHTML]="firstMetadataValue('dc.publisher') + ', '"></span><span
*ngIf="dso.hasMetadata('dc.date.issued')" class="item-list-date"
[innerHTML]="firstMetadataValue('dc.date.issued')"></span>)
<span *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
class="item-list-authors">
<span *ngFor="let author of getValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">
<span *ngFor="let author of allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">
<span [innerHTML]="author"><span [innerHTML]="author"></span></span>
</span>
</span>
</ds-truncatable-part>
</span>
<div *ngIf="dso.findMetadata('dc.description.abstract')" class="item-list-abstract">
<div *ngIf="dso.hasMetadata('dc.description.abstract')" class="item-list-abstract">
<ds-truncatable-part [id]="dso.id" [minLines]="3"><span
[innerHTML]="getFirstValue('dc.description.abstract')"></span>
[innerHTML]="firstMetadataValue('dc.description.abstract')"></span>
</ds-truncatable-part>
</div>
</ds-truncatable>

View File

@@ -3,11 +3,10 @@ import { Observable } from 'rxjs';
import { SearchResult } from '../../../+search-page/search-result.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { Metadatum } from '../../../core/shared/metadatum.model';
import { hasNoValue, isEmpty } from '../../empty.util';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { TruncatableService } from '../../truncatable/truncatable.service';
import { Metadata } from '../../../core/shared/metadata.model';
@Component({
selector: 'ds-search-result-list-element',
@@ -22,39 +21,14 @@ export class SearchResultListElementComponent<T extends SearchResult<K>, K exten
this.dso = this.object.dspaceObject;
}
getValues(keys: string[]): string[] {
const results: string[] = new Array<string>();
this.object.hitHighlights.forEach(
(md: Metadatum) => {
if (keys.indexOf(md.key) > -1) {
results.push(md.value);
}
}
);
if (isEmpty(results)) {
this.dso.filterMetadata(keys).forEach(
(md: Metadatum) => {
results.push(md.value);
}
);
}
return results;
/** Gets all matching metadata string values from hitHighlights or dso metadata, preferring hitHighlights. */
allMetadataValues(keyOrKeys: string | string[]): string[] {
return Metadata.allValues([this.object.hitHighlights, this.dso.metadata], keyOrKeys);
}
getFirstValue(key: string): string {
let result: string;
this.object.hitHighlights.some(
(md: Metadatum) => {
if (key === md.key) {
result = md.value;
return true;
}
}
);
if (hasNoValue(result)) {
result = this.dso.findMetadata(key);
}
return result;
/** Gets the first matching metadata string value from hitHighlights or dso metadata, preferring hitHighlights. */
firstMetadataValue(keyOrKeys: string | string[]): string {
return Metadata.firstValue([this.object.hitHighlights, this.dso.metadata], keyOrKeys);
}
isCollapsed(): Observable<boolean> {