- add citation for dc:source inside datasetxml2oai.pmh.xslt

- add ddc collection for ListSets request
- add ddc category into the header of GetReord and GetRecords requests
- npm updates
This commit is contained in:
Arno Kaimbacher 2022-12-02 12:26:25 +01:00
parent ab57fabc36
commit b1d62e14f7
10 changed files with 322 additions and 23 deletions

View File

@ -30,7 +30,7 @@
expand-text="yes" expand-text="yes"
version="3.0"> version="3.0">
<xsl:output method="xml" encoding="iso-8859-1" indent="yes"/> <xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/> <xsl:mode on-no-match="shallow-copy"/>
<xsl:import href="functions.xslt"/> <xsl:import href="functions.xslt"/>

View File

@ -34,7 +34,7 @@
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" encoding="iso-8859-1" indent="yes" /> <xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:template match="Rdr_Dataset" mode="oai_datacite"> <xsl:template match="Rdr_Dataset" mode="oai_datacite">
<resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

View File

@ -15,7 +15,7 @@
<xsl:include href="assets/oai_2_iso19139.xslt" /> <xsl:include href="assets/oai_2_iso19139.xslt" />
<xsl:output method="xml" indent="yes" encoding="iso-8859-1" /> <xsl:output method="xml" indent="yes" encoding="utf-8" />
<xsl:param name="responseDate" /> <xsl:param name="responseDate" />
<xsl:param name="unixTimestamp" /> <xsl:param name="unixTimestamp" />
@ -431,9 +431,45 @@
<xsl:if test="EmbargoDate and ($unixTimestamp &lt; EmbargoDate/@UnixTimestamp)"> <xsl:if test="EmbargoDate and ($unixTimestamp &lt; EmbargoDate/@UnixTimestamp)">
<dc:rights>embargo</dc:rights> <dc:rights>embargo</dc:rights>
</xsl:if> </xsl:if>
<!-- dc:source -->
<xsl:call-template name="citation"/>
</oai_dc:dc> </oai_dc:dc>
</xsl:template> </xsl:template>
<xsl:template name="citation">
<dc:source>
<xsl:variable name="creatorName">
<xsl:for-each select="*[name() = 'PersonAuthor']">
<xsl:variable name="person" select="."/>
<xsl:variable name="uppercase" select="'ABC..XYZ'"/>
<xsl:variable name="lowercase" select="'abc..zyz'"/>
<xsl:variable name="authorName">
<xsl:choose>
<xsl:when test="string($person/@FirstName)">
<xsl:variable name="name" select="concat(
$person/@LastName, ', ', concat(translate(substring($person/@FirstName, 1, 1), $lowercase, $uppercase), '.')
)" />
<xsl:value-of select="$name"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$person/@LastName"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="normalize-space($authorName)"/>
<xsl:choose>
<xsl:when test="position() != last()">,</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="year" select="concat( ' (', string(ServerDatePublished/@Year), '): ' )" />
<xsl:variable name="mainTitle" select="string(TitleMain/@Value)" />
<xsl:variable name="creatingCorporation" select="concat('.', string(@CreatingCorporation), ', ')" />
<xsl:variable name="publisherName" select="string(@PublisherName)" />
<xsl:value-of select="concat($creatorName, $year, $mainTitle, $creatingCorporation, $publisherName, ', Wien')"/>
</dc:source>
</xsl:template>
<xsl:template match="Coverage" mode="oai_dc"> <xsl:template match="Coverage" mode="oai_dc">
<dc:coverage> <dc:coverage>
<xsl:variable name="geolocation" select="concat( <xsl:variable name="geolocation" select="concat(

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,7 @@ import { readFileSync } from "fs";
// @ts-ignore // @ts-ignore
import { transform } from "saxon-js"; import { transform } from "saxon-js";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import { Dataset, Project, License } from "../models/init-models"; import { Dataset, Project, License, Collection, CollectionRole } from "../models/init-models";
import Logger from "jet-logger"; import Logger from "jet-logger";
import { BadOaiModelException, OaiModelException } from "../exceptions/OaiModelException"; import { BadOaiModelException, OaiModelException } from "../exceptions/OaiModelException";
import PageNotFoundException from "../exceptions/PageNotFoundException"; import PageNotFoundException from "../exceptions/PageNotFoundException";
@ -255,6 +255,7 @@ export class OaiController {
// 'bibliography:true' => 'Set for bibliographic entries', // 'bibliography:true' => 'Set for bibliographic entries',
// 'bibliography:false' => 'Set for non-bibliographic entries', // 'bibliography:false' => 'Set for non-bibliographic entries',
...(await this.getSetsForDatasetTypes()), ...(await this.getSetsForDatasetTypes()),
...(await this.getSetsForCollections()),
// ... await this.getSetsForProjects(), // ... await this.getSetsForProjects(),
} as IDictionary; } as IDictionary;
@ -437,6 +438,7 @@ export class OaiController {
if ("set" in oaiRequest) { if ("set" in oaiRequest) {
const set = oaiRequest["set"] as string; const set = oaiRequest["set"] as string;
const setArray = set.split(":"); const setArray = set.split(":");
if (setArray[0] == "data-type") { if (setArray[0] == "data-type") {
if (setArray.length == 2 && setArray[1]) { if (setArray.length == 2 && setArray[1]) {
andArray.push({ andArray.push({
@ -458,6 +460,26 @@ export class OaiController {
}, },
}; };
includeArray.push(icncludeFilter); includeArray.push(icncludeFilter);
} else if (setArray[0] == "ddc") {
// const openAccessLicences = ["CC-BY-4.0", "CC-BY-SA-4.0"];
if (setArray.length == 2 && setArray[1] != "") {
const icncludeFilter = {
model: Collection,
as: "collections",
required: true, //return only records which have an associated model INNER JOIN
where: {
number: setArray[1],
},
// include: [
// {
// model: CollectionRole,
// attributes: ["oai_name"],
// as: "collectionRole",
// },
// ],
};
includeArray.push(icncludeFilter);
}
} }
} }
@ -612,7 +634,7 @@ export class OaiController {
[Sequelize.Op.in]: workIds, [Sequelize.Op.in]: workIds,
}, },
}, },
include: ["xmlCache"], include: ["xmlCache", { model: Collection, as: "collections" }],
order: ["publish_id"], order: ["publish_id"],
}); });
for (const dataset of datasets) { for (const dataset of datasets) {
@ -718,6 +740,14 @@ export class OaiController {
dataset.publish_id && this.addLandingPageAttribute(domNode, dataset.publish_id.toString()); dataset.publish_id && this.addLandingPageAttribute(domNode, dataset.publish_id.toString());
// } // }
this.addSpecInformation(domNode, "data-type:" + dataset.type); this.addSpecInformation(domNode, "data-type:" + dataset.type);
if (dataset.collections) {
for (const coll of dataset.collections) {
const collRole = await coll.getCollectionRole();
this.addSpecInformation(domNode, collRole.oai_name + ":" + coll.number);
}
}
datasetNode.import(domNode); datasetNode.import(domNode);
} }
@ -758,6 +788,33 @@ export class OaiController {
return sets; return sets;
} }
private async getSetsForCollections(): Promise<IDictionary> {
const sets: { [key: string]: string } = {} as IDictionary;
const collections: Array<Collection> = await Collection.findAll({
attributes: ["name", "number"],
include: [
{
model: CollectionRole,
attributes: ["oai_name"],
as: "collectionRole",
required: true, //return only records which have an associated model role -> INNER JOIN
where: {
visible_oai: true,
},
},
],
});
collections.forEach((collection) => {
// if collection has a collection role (classification like ddc):
if (collection.number) {
const setSpec = collection.collectionRole?.oai_name + ":" + collection.number;
sets[setSpec] = `Set ${collection.number} '${collection.name}'`;
}
});
return sets;
}
private async getSetsForDatasetTypes(): Promise<IDictionary> { private async getSetsForDatasetTypes(): Promise<IDictionary> {
const sets: { [key: string]: string } = {} as IDictionary; const sets: { [key: string]: string } = {} as IDictionary;

View File

@ -12,12 +12,12 @@ export enum OaiErrorCodes {
// https://medium.com/@juliapassynkova/map-your-typescript-enums-e402d406b229 // https://medium.com/@juliapassynkova/map-your-typescript-enums-e402d406b229
export const OaiModelError = new Map<number, string>([ export const OaiModelError = new Map<number, string>([
[OaiErrorCodes.BADVERB, 'badVerb'], [OaiErrorCodes.BADVERB, "badVerb"],
[OaiErrorCodes.BADARGUMENT, 'badArgument'], [OaiErrorCodes.BADARGUMENT, "badArgument"],
[OaiErrorCodes.NORECORDSMATCH, 'noRecordsMatch'], [OaiErrorCodes.NORECORDSMATCH, "noRecordsMatch"],
[OaiErrorCodes.CANNOTDISSEMINATEFORMAT, 'cannotDisseminateFormat'], [OaiErrorCodes.CANNOTDISSEMINATEFORMAT, "cannotDisseminateFormat"],
[OaiErrorCodes.BADRESUMPTIONTOKEN, 'badResumptionToken'], [OaiErrorCodes.BADRESUMPTIONTOKEN, "badResumptionToken"],
[OaiErrorCodes.IDDOESNOTEXIST, 'idDoesNotExist'] [OaiErrorCodes.IDDOESNOTEXIST, "idDoesNotExist"],
]); ]);
// class OaiModelError { // class OaiModelError {
@ -28,7 +28,6 @@ export const OaiModelError = new Map<number, string>([
// // const NORECORDSMATCH = 1014; // // const NORECORDSMATCH = 1014;
// // const IDDOESNOTEXIST = 1015; // // const IDDOESNOTEXIST = 1015;
// protected static $oaiErrorCodes = { // protected static $oaiErrorCodes = {
// OaiErrorCodes. 'badVerb', // OaiErrorCodes. 'badVerb',
// BADARGUMENT : 'badArgument', // BADARGUMENT : 'badArgument',
@ -38,7 +37,6 @@ export const OaiModelError = new Map<number, string>([
// IDDOESNOTEXIST: 'idDoesNotExist', // IDDOESNOTEXIST: 'idDoesNotExist',
// }; // };
// public static function mapCode($code) // public static function mapCode($code)
// { // {
// if (false === array_key_exists($code, self::$oaiErrorCodes)) { // if (false === array_key_exists($code, self::$oaiErrorCodes)) {

91
src/models/Collection.ts Normal file
View File

@ -0,0 +1,91 @@
// import Sequelize from "sequelize";
import {
Model,
DataTypes,
InferAttributes,
InferCreationAttributes,
CreationOptional,
NonAttribute,
Association,
BelongsToGetAssociationMixin,
} from "sequelize";
import sequelizeConnection from "../config/db.config";
import CollectionRole from "./CollectionRole";
import Dataset from "./Dataset";
class Collection extends Model<InferAttributes<Collection>, InferCreationAttributes<Collection>> {
// id can be undefined during creation when using `autoIncrement`
declare id: CreationOptional<number>;
declare role_id: number | null;
declare number: string | null; // nullable fields
declare name: string; // not nullable fields
declare oai_subset: string | null; // nullable fields
declare parent_id: number | null;
declare visible: boolean;
declare visible_publish: boolean;
// https://sequelize.org/docs/v6/other-topics/typescript/
// Since TS cannot determine model association at compile time
// we have to declare them here purely virtually
// these will not exist until `Model.init` was called.
declare getCollectionRole: BelongsToGetAssociationMixin<CollectionRole>;
// You can also pre-declare possible inclusions, these will only be populated if you
// actively include a relation.belongsTo one collectionRole
declare collectionRole?: NonAttribute<CollectionRole>;
declare datasets?: NonAttribute<Dataset>;
declare static associations: {
collectionRole: Association<Collection, CollectionRole>;
datasets: Association<Collection, Dataset>;
};
// getters that are not attributes should be tagged using NonAttribute
// to remove them from the model's Attribute Typings.
get fullName(): NonAttribute<string> {
return this.name + ":" + this.number;
}
}
Collection.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
},
role_id: {
type: DataTypes.INTEGER,
allowNull: true,
},
number: {
type: DataTypes.STRING(255),
allowNull: true,
},
name: {
type: DataTypes.STRING(255),
allowNull: false,
},
oai_subset: {
type: DataTypes.STRING(255),
allowNull: true,
},
parent_id: {
type: DataTypes.INTEGER,
allowNull: true,
},
visible: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
visible_publish: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
},
{
timestamps: false,
sequelize: sequelizeConnection,
tableName: "collections",
},
);
export default Collection;

View File

@ -0,0 +1,62 @@
// import Sequelize from "sequelize";
import { Model, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, Association, NonAttribute } from "sequelize";
import sequelizeConnection from "../config/db.config";
import Collection from "./Collection";
class CollectionRole extends Model<InferAttributes<CollectionRole>, InferCreationAttributes<CollectionRole>> {
// id can be undefined during creation when using `autoIncrement`
declare id: CreationOptional<number>;
declare name: string; // not nullable fields
declare oai_name: string; // not nullable fields
declare position: number;
declare visible: boolean;
declare visible_frontdoor: boolean;
declare visible_oai: boolean;
// You can also pre-declare possible inclusions, these will only be populated if you
// actively include a relation.
declare collections?: NonAttribute<Collection>;
declare static associations: {
collections: Association<CollectionRole, Collection>;
};
}
CollectionRole.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
},
name: {
type: DataTypes.STRING(255),
allowNull: false,
},
oai_name: {
type: DataTypes.STRING(255),
allowNull: false,
},
position: {
type: DataTypes.INTEGER,
allowNull: false,
},
visible: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
visible_frontdoor: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
visible_oai: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
},
{
timestamps: false,
sequelize: sequelizeConnection,
tableName: "collections_roles",
},
);
export default CollectionRole;

View File

@ -52,6 +52,7 @@
import { Op, Model, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, Association } from "sequelize"; import { Op, Model, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, Association } from "sequelize";
import sequelizeConnection from "../config/db.config"; import sequelizeConnection from "../config/db.config";
import DocumentXmlCache from "./DocumentXmlCache"; import DocumentXmlCache from "./DocumentXmlCache";
import Collection from "./Collection";
class Dataset extends Model<InferAttributes<Dataset>, InferCreationAttributes<Dataset>> { class Dataset extends Model<InferAttributes<Dataset>, InferCreationAttributes<Dataset>> {
// id can be undefined during creation when using `autoIncrement` // id can be undefined during creation when using `autoIncrement`
@ -76,6 +77,11 @@ class Dataset extends Model<InferAttributes<Dataset>, InferCreationAttributes<Da
// actively include a relation. // actively include a relation.
declare xmlCache?: NonAttribute<DocumentXmlCache>; // Note this is optional since it's only populated when explicitly requested in code declare xmlCache?: NonAttribute<DocumentXmlCache>; // Note this is optional since it's only populated when explicitly requested in code
declare collections?: NonAttribute<Collection[]>;
// declare static associations: {
// };
// getters that are not attributes should be tagged using NonAttribute // getters that are not attributes should be tagged using NonAttribute
// to remove them from the model's Attribute Typings. // to remove them from the model's Attribute Typings.
get fullName(): NonAttribute<string | null> { get fullName(): NonAttribute<string | null> {
@ -84,6 +90,7 @@ class Dataset extends Model<InferAttributes<Dataset>, InferCreationAttributes<Da
declare static associations: { declare static associations: {
xmlCache: Association<Dataset, DocumentXmlCache>; xmlCache: Association<Dataset, DocumentXmlCache>;
collections: Association<Dataset, Collection>;
}; };
public static async earliestPublicationDate(): Promise<Dataset | null> { public static async earliestPublicationDate(): Promise<Dataset | null> {

View File

@ -15,10 +15,27 @@ import Project from "./Project";
import File from "./File"; import File from "./File";
import Identifier from "./Identifier"; import Identifier from "./Identifier";
import DocumentXmlCache from "./DocumentXmlCache"; import DocumentXmlCache from "./DocumentXmlCache";
import CollectionRole from "./CollectionRole";
import Collection from "./Collection";
const dbContext = initModels(); const dbContext = initModels();
export { Dataset, Title, Abstract, User, Person, Subject, Coverage, License, Project, Identifier, DocumentXmlCache, File }; export {
Dataset,
Title,
Abstract,
User,
Person,
Subject,
Coverage,
License,
Project,
Identifier,
DocumentXmlCache,
File,
Collection,
CollectionRole,
};
export default dbContext; export default dbContext;
export function initModels() { export function initModels() {
@ -201,6 +218,37 @@ export function initModels() {
as: "dataset", as: "dataset",
}); });
// collection an collectionRole relations
CollectionRole.hasMany(Collection, {
as: "collections",
foreignKey: "role_id",
});
Collection.belongsTo(CollectionRole, {
foreignKey: "role_id",
as: "collectionRole",
});
// dataset and collections
//licences
const DatasetCollection = sequelizeConnection.define(
"link_documents_collections",
{},
{
tableName: "link_documents_collections",
timestamps: false,
},
);
Dataset.belongsToMany(Collection, {
through: DatasetCollection,
as: "collections",
foreignKey: "document_id",
});
Collection.belongsToMany(Dataset, {
through: DatasetCollection,
foreignKey: "collection_id",
as: "datasets",
});
return { return {
Dataset: Dataset, Dataset: Dataset,
Title: Title, Title: Title,