diff --git a/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.html b/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.html
index f0644e4..9d47132 100644
--- a/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.html
+++ b/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.html
@@ -1 +1,57 @@
-under development...
+
+
+
+
diff --git a/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.ts b/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.ts
index a7d4aa5..eb748c9 100644
--- a/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.ts
+++ b/src/app/pages/oaipmh-validator/validation-settings/oaipmh-validator.component.ts
@@ -1,5 +1,8 @@
-import {Component, OnInit, ViewChild} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
import {OaipmhValidatorService} from "../../../services/oaipmh-validator.service";
+import {Option} from "../../../shared/utils/input/input.component";
+import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms";
+import {StringUtils} from "../../../shared/utils/string-utils.class";
@Component({
selector: 'app-oaipmh-validator',
@@ -7,9 +10,46 @@ import {OaipmhValidatorService} from "../../../services/oaipmh-validator.service
styleUrls: ['./oaipmh-validator.component.less']
})
export class OaipmhValidatorComponent implements OnInit {
- constructor(private validator: OaipmhValidatorService) {}
+ public options: Option[] = [
+ {label: 'OpenAIRE Guidelines for Data Archives Profile v2', value: 'OpenAIRE Guidelines for Data Archives Profile v2'},
+ {label: 'OpenAIRE Guidelines for Literature Repositories Profile v3', value: 'OpenAIRE Guidelines for Literature Repositories Profile v3'},
+ {label: 'OpenAIRE Guidelines for Literature Repositories Profile v4', value: 'OpenAIRE Guidelines for Literature Repositories Profile v4'},
+ {label: 'OpenAIRE FAIR Guidelines for Data Repositories Profile', value: 'OpenAIRE FAIR Guidelines for Data Repositories Profile'}
+ ];
+ public form: UntypedFormGroup;
+ public sets: Option[] = [{label: 'All sets', value: 'all'}];
+ public recordsNum: number = 10;
- ngOnInit() {
- console.log("under development...");
+ constructor(private fb: UntypedFormBuilder, private validator: OaipmhValidatorService) {
+ this.form = this.fb.group({
+ url: this.fb.control("", StringUtils.urlValidator()),//[Validators.required/*, Validators.email*/]),
+ guidelines: this.fb.control("", Validators.required),
+ recordsNum: this.fb.control(null, Validators.required),
+ set: this.fb.control('', Validators.required)
+ });
+ }
+
+ ngOnInit() {}
+
+ updateRecordsNum(increase: boolean = true) {
+ this.recordsNum = this.recordsNum + (increase ? 10 : -10);
+ this.form.get('recordsNum').setValue(this.recordsNum);
+ }
+
+ getSets() {
+ let options: Option[] = [{label: 'All sets', value: 'all'}];
+ if(this.form.get('url') && this.form.get('url').value && StringUtils.isValidUrl(this.form.get('url').value)) {
+ this.validator.getSets(this.form.get('url').value).subscribe(sets => {
+ for(let set of sets) {
+ options.push({label: set['setName'], value: set['setSpec']});
+ }
+ this.sets = options;
+ });
+ }
+ }
+
+ validate() {
+ console.log(this.form.value);
+ this.validator.validate(this.form.get('guidelines').value, this.form.get('url').value, this.form.get('recordsNum').value, this.form.get('set').value);
}
}
diff --git a/src/app/services/oaipmh-validator.service.ts b/src/app/services/oaipmh-validator.service.ts
index 49605fe..32c883f 100644
--- a/src/app/services/oaipmh-validator.service.ts
+++ b/src/app/services/oaipmh-validator.service.ts
@@ -1,6 +1,7 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {environment} from "../../environments/environment";
+import {map} from "rxjs";
@Injectable({
providedIn: "root"
@@ -28,4 +29,15 @@ export class OaipmhValidatorService {
let url: string = environment.validatorAPI + "reports/getJobResult?jobId="+jobId;
return this.http.get(url);
}
+
+ getSets(baseUrl) {
+ let url: string = environment.validatorAPI + "getSets?baseUrl="+baseUrl;
+ return this.http.get(url).pipe(map(res => res['set']));
+ }
+
+ validate(guidelines: string, baseUrl: string, numberOfRecords: string, set: string) {
+ let url: string = environment.validatorAPI + "realValidator?guidelines="+guidelines+"&baseUrl="+baseUrl
+ +(numberOfRecords ? ("&numberOfRecords="+numberOfRecords) : "") + (set ? ("&set="+set) : "");
+ console.log(url);
+ }
}
diff --git a/src/app/shared/utils/string-utils.class.ts b/src/app/shared/utils/string-utils.class.ts
new file mode 100644
index 0000000..8eb8ab1
--- /dev/null
+++ b/src/app/shared/utils/string-utils.class.ts
@@ -0,0 +1,455 @@
+import {UrlSegment} from '@angular/router';
+import {AbstractControl, UntypedFormGroup, ValidationErrors, ValidatorFn, Validators} from "@angular/forms";
+
+export class Dates {
+ public static yearMin = 1800;
+ public static yearMax = (new Date().getFullYear()) + 10;
+ public static currentYear = (new Date().getFullYear());
+
+ public static isValidYear(yearString, yearMin = this.yearMin, yearMax = this.yearMax) {
+ // First check for the pattern
+ if (!/^\d{4}$/.test(yearString))
+ return false;
+ var year = parseInt(yearString, 10);
+
+ // Check the ranges of month and year
+ return !(year < yearMin || year > yearMax);
+ }
+
+ //format YYYY-MM-DD
+ public static isValidDate(dateString: string) {
+ // First check for the pattern
+ if (!/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(dateString))
+ return false;
+
+ // Parse the date parts to integers
+ var parts = dateString.split("-");
+ var day = parseInt(parts[2], 10);
+ var month = parseInt(parts[1], 10);
+ var year = parseInt(parts[0], 10);
+ if (!this.isValidYear(parts[0])) {
+ return false;
+ }
+
+ // Check the ranges of month and year
+ if (month == 0 || month > 12)
+ return false;
+
+ var monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+ // Adjust for leap years
+ if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0))
+ monthLength[1] = 29;
+
+ // Check the range of the day
+ return day > 0 && day <= monthLength[month - 1];
+
+ }
+
+ public static getDateToday(): Date {
+ var myDate = new Date();
+ return myDate;
+
+ }
+
+ public static getDateToString(myDate: Date): string {
+ var date: string = myDate.getFullYear() + "-";
+ date += ((myDate.getMonth() + 1) < 10) ? "0" + (myDate.getMonth() + 1) : (myDate.getMonth() + 1);
+ date += "-";
+ date += (myDate.getDate() < 10) ? "0" + myDate.getDate() : myDate.getDate();
+ return date;
+
+ }
+
+ public static getDateXMonthsAgo(x: number): Date {
+ var myDate = new Date();
+ myDate.setMonth(myDate.getMonth() - x);
+ return myDate;
+
+ }
+
+ public static getDateXYearsAgo(x: number): Date {
+ var myDate = new Date();
+ myDate.setFullYear(myDate.getFullYear() - x);
+ return myDate;
+
+ }
+
+ public static getDateFromString(date: string): Date {
+
+ var myDate = new Date();
+ myDate.setFullYear(+date.substring(0, 4));
+ myDate.setMonth(((date.length > 5) ? (+date.substring(5, 7) - 1) : (0)));
+ myDate.setDate(((date.length > 8) ? (+date.substring(8, 11)) : (1)));
+ return myDate;
+
+ }
+
+ public static getDate(dateString: string): Date {
+ let date = new Date(dateString);
+ if (Object.prototype.toString.call(date) === "[object Date]") {
+ if (isNaN(date.getTime())) {
+ return null;
+ } else {
+ return date;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public static timeSince(date: Date) {
+
+ let seconds = Math.floor((new Date().getTime() - new Date(date).getTime()) / 1000);
+
+ let interval = seconds / (365*24*60*60);
+
+ if (interval > 1) {
+ let years = Math.floor(interval);
+ return (years > 1?(years + ' years ago'):'a year ago');
+ }
+ interval = seconds / (7*24*60*60);
+ if (interval > 1) {
+ let weeks = Math.floor(interval);
+ return (weeks > 1?(weeks + ' weeks ago'):'a week ago');
+ }
+ interval = seconds / (24*60*60);
+ if (interval > 1) {
+ let days = Math.floor(interval);
+ return (days > 1?(days + ' days ago'):'a day ago');
+ }
+ interval = seconds / (60*60);
+ if (interval > 1) {
+ let hours = Math.floor(interval);
+ return (hours > 1?(hours + ' hours ago'):'an hour ago');
+ }
+ interval = seconds / 60;
+ if (interval > 1) {
+ let minutes = Math.floor(interval);
+ return (minutes > 1?(minutes + ' minutes ago'):'a minute ago');
+ }
+ seconds = Math.floor(interval);
+ return (seconds > 1?(seconds + ' seconds ago'):' just now');
+ }
+}
+
+export class DOI {
+
+ public static getDOIsFromString(str: string): string[] {
+ return Identifier.getDOIsFromString(str);
+ }
+
+ public static isValidDOI(str: string): boolean {
+ return Identifier.isValidDOI(str);
+ }
+}
+
+export class Identifier {
+ class: "doi" | "pmc" | "pmid" | "handle" | "ORCID" | "re3data" = null;
+ id: string;
+
+ public static getDOIsFromString(str: string): string[] {
+ var DOIs: string[] = [];
+ var words: string[] = str.split(" ");
+
+ for (var i = 0; i < words.length; i++) {
+ let id = words[i];
+ if (DOI.isValidDOI(id) ) {
+ id = Identifier.getRawDOIValue(id);
+ if( DOIs.indexOf(id) == -1){
+ DOIs.push(id);
+ }
+ }
+ }
+ return DOIs;
+ }
+ public static getRawDOIValue(id: string): string {
+ if(id.indexOf("doi.org")!=-1 && id.split("doi.org/").length > 1){
+ id = id.split("doi.org/")[1];
+ }
+ return id;
+ }
+ public static getIdentifiersFromString(str: string): Identifier[] {
+ let identifiers: Identifier[] = [];
+ let words: string[] = str.split(" ");
+
+ for (let id of words) {
+ if (id.length > 0) {
+ let identifier: Identifier = this.getIdentifierFromString(id);
+ if (identifier) {
+ identifiers.push(identifier);
+ }
+ }
+ }
+ return identifiers;
+ }
+
+ public static getIdentifierFromString(pid: string,strict:boolean = true): Identifier {
+ if (Identifier.isValidDOI(pid)) {
+ pid = Identifier.getRawDOIValue(pid);
+ return {"class": "doi", "id": pid};
+ } else if (Identifier.isValidORCID(pid)) {
+ return {"class": "ORCID", "id": pid};
+ } else if (Identifier.isValidPMCID(pid)) {
+ return {"class": "pmc", "id": pid};
+ } else if (Identifier.isValidPMID(pid)) {
+ return {"class": "pmid", "id": pid};
+ } else if (Identifier.isValidHANDLE(pid)) {
+ return {"class": "handle", "id": pid};
+ } else if (Identifier.isValidRe3Data(pid)) {
+ return {"class": "re3data", "id": pid};
+ }
+ //set it as a doi, to catch the case that doi has not valid format
+ return (strict?null:{"class": "doi", "id": pid});
+ }
+
+ public static getPIDFromIdentifiers(identifiers: Map): Identifier {
+ let classes:string [] = ["doi", "handle", "pmc", "pmid", "re3data"];
+ if(identifiers) {
+ for (let cl of classes) {
+ if (identifiers.get(cl)) {
+ for (let pid of identifiers.get(cl)) {
+ let identifier = Identifier.getIdentifierFromString(pid);
+ if (identifier) {
+ return identifier;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static isValidDOI(str: string): boolean {
+ //keep only exp3?
+ let exp1 = /\b(10[.][0-9]{4,}(?:[.][0-9]+)*\/(?:(?!["&\'<>])\S)+)\b/g;
+ let exp2 = /\b(10[.][0-9]{4,}(?:[.][0-9]+)*\/(?:(?!["&\'<>])[[:graph:]])+)\b/g;
+ let exp3 = /\b(10[.]*)\b/g;
+ return (str.match(exp1) != null || str.match(exp2) != null || str.match(exp3) != null);
+ }
+
+ public static isValidORCID(str: string): boolean {
+ let exp = /\b\d{4}-\d{4}-\d{4}-(\d{3}X|\d{4})\b/g;
+ return str.match(exp) != null;
+ }
+
+ public static isValidPMID(str: string): boolean {
+ let exp = /^\d*$/g;
+ return str.match(exp) != null;
+
+ }
+
+ public static isValidPMCID(str: string): boolean {
+ let exp = /^(PMC\d{7})$/g;
+ return str.match(exp) != null;
+ }
+
+ public static isValidHANDLE(str: string): boolean {
+ let exp = /^[0-9a-zA-Z-]*\/[0-9a-zA-Z-]*$/g;
+ return str.match(exp) != null;
+ }
+
+ public static isValidRe3Data(str: string): boolean {
+ let exp = /(r3d[1-9]\d{0,8})/g;
+ return str.match(exp) != null;
+ }
+}
+
+export class StringUtils {
+
+ public static urlRegex = 'https?:\\/\\/(?:www(2?)\\.|(?!www(2?)))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www(2?)\\.' +
+ '[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www(2?)\\.|(?!www(2?)))[a-zA-Z0-9]+\\.[^\\s]{2,}|www(2?)\\.' +
+ '[a-zA-Z0-9]+\\.[^\\s]{2,}';
+
+ public static routeRegex = '^[a-zA-Z0-9\/][a-zA-Z0-9\/-]*$';
+
+ public static jsonRegex = /^[\],:{}\s]*$/;
+
+ public static urlPrefix(url: string): string {
+ if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
+ return "";
+ } else {
+ return "//";
+ }
+ }
+
+ public static quote(params: string): string {
+ return '"' + params + '"';
+ }
+
+ public static unquote(params: string): string {
+ if (params.length > 2 && (params[0] == '"' && params[params.length - 1] == '"') || (params[0] == "'" && params[params.length - 1] == "'")) {
+ params = params.substring(1, params.length - 1);
+ }
+ return params;
+ }
+
+ public static URIEncode(params: string): string {
+ return encodeURIComponent(params);
+ }
+
+ public static URIDecode(params: string): string {
+ return decodeURIComponent(params);
+ }
+
+ public static validateEmails(emails: string): boolean {
+ return (emails.split(',')
+ .map(email => Validators.email({value: email.trim()}))
+ .find(_ => _ !== null) === undefined);
+ }
+
+ public static b64DecodeUnicode(str) {
+ return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ }).join(''));
+ }
+
+ private emailValidator(email: any): boolean {
+ return !!email.match("^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$");
+ }
+
+ public static isValidUrl(url: string): boolean {
+ return new RegExp(this.urlRegex).test(url);
+ }
+
+ public static urlValidator(): ValidatorFn {
+ return Validators.pattern(StringUtils.urlRegex);
+ }
+
+ public static jsonValidator(error: string = ''): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ if(control.value) {
+ let test = control.getRawValue().replace(/\\["\\\/bfnrtu]/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+ if(!new RegExp(this.jsonRegex).test(test)) {
+ return {error: 'Please provide a valid JSON.' + error}
+ }
+ }
+ return null;
+ };
+ }
+
+ public static validatorType(options: string[]): ValidatorFn {
+ return (control: AbstractControl): { [key: string]: boolean } | null => {
+ if (options.filter(type => type === control.value).length === 0) {
+ return {'type': false};
+ }
+ return null;
+ }
+ }
+
+ public static validRoute(pages: any[], field: string, initial: string = null): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ if(control.value) {
+ if(!new RegExp(this.routeRegex).test(control.value)) {
+ return {error: 'Route should contain only letters or numbers, e.g /route or route'}
+ }
+ if(pages && pages.length > 0 && control.value !== initial) {
+ const forbidden = pages.filter(page => page[field].replace('/', '') === control.value.replace('/', '')).length > 0;
+ return forbidden ? {error: 'This route is used by an other page'} : null;
+ }
+ }
+ return null;
+ };
+ }
+
+ public static sliceString(mystr, size: number): string {
+ const sliced = String(mystr).substr(0, size);
+ return sliced + (String(mystr).length > size ? '...' : '');
+ }
+
+ /**
+ * Splits a text to words base on a list of separators. Returns the words of the text including the separators.
+ * DO NOT TOUCH, IT WORKS
+ *
+ * @param text
+ * @param separators
+ */
+ public static split(text: string, separators: string[]): string[] {
+ let words: (string | string[])[] = [text];
+ separators.forEach(separator => {
+ words.forEach((word, index) => {
+ if (typeof word === "string" && separators.indexOf(word) === -1) {
+ let tokens: string[] = word.split(separator).filter(value => value !== '');
+ if (tokens.length > 1) {
+ words[index] = [];
+ tokens.forEach((token, i) => {
+ ((words[index])).push(token);
+ if (i !== (tokens.length - 1)) {
+ ((words[index])).push(separator);
+ }
+ });
+ }
+ }
+ });
+ words = [].concat.apply([], words);
+ });
+ return words;
+ }
+
+ public static capitalize(value: string): string {
+ return value.charAt(0).toUpperCase() + value.slice(1);
+ }
+
+ /**
+ * Checks if a text contains a word
+ */
+ public static containsWord(text: string, word: string): boolean {
+ return (text && text.toLowerCase().includes(word));
+ }
+
+ public static URLSegmentsToPath(segments: UrlSegment[]): string {
+ let path = '';
+ segments.forEach(route => {
+ path += '/' + route.path;
+ })
+ return path;
+ }
+
+ public static isEuropeanCountry(country: string) {
+ let countries = ["Albania", "Andorra", "Armenia", "Austria", "Azerbaijan", "Belarus", "Belgium", "Bosnia and Herzegovina",
+ "Bulgaria", "Croatia", "Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Georgia", "Germany", "Greece", "Hungary", "Iceland", "Ireland",
+ "Italy", "Kosovo", "Latvia", "Liechtenstein", "Lithuania", "Luxembourg", "Macedonia", "Malta", "Moldova", "Monaco", "Montenegro", "The Netherlands", "Norway", "Poland",
+ "Portugal", "Romania", "Russia", "San Marino", "Serbia", "Slovakia", "Slovenia", "Spain", "Sweden", "Switzerland", "Turkey", "Ukraine", "United Kingdom", "Vatican City",
+ ];
+ return (country && countries.indexOf(country) != -1);
+ }
+
+ public static isOpenAIREID(id: string) {
+ if (id && id.length == 46) {
+ let exp1 = /^.{12}::([0-9a-z]{32})$/g;
+ return (id.match(exp1) != null);
+ }
+ return false;
+ }
+ public static HTMLToString( html:string){
+ try {
+ html = html.replace(/ /g, ' ');
+ html = html.replace(/(\r\n|\n|\r| +(?= ))|\s\s+/gm, " ");
+ html = html.replace(/<[^>]*>/g, '');
+ }catch( e){
+ }
+ return html;
+ }
+
+ public static inValidYearValidator(minYear, maxYear): ValidatorFn {
+ return (control: AbstractControl): {[key: string]: any} | null => {
+ return ((control.value && !Dates.isValidYear(control.value, minYear, maxYear)) ?
+ {error: 'Year must be between ' + minYear + ' and ' + maxYear + '.'} : null);
+ };
+ }
+
+ public static fromYearAfterToYearValidator: ValidatorFn = (control: UntypedFormGroup): ValidationErrors | null => {
+ const yearFrom = control.get('yearFrom');
+ const yearTo = control.get('yearTo');
+ return ((yearFrom && yearTo && (parseInt(yearFrom.value, 10) > parseInt(yearTo.value, 10))) ?
+ {error: 'Starting year must be greater than or equal to ending year.' } : null);
+ }
+
+ public static rangeRequired( enabled:boolean): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const yearFrom = control.get('yearFrom');
+ const yearTo = control.get('yearTo');
+ return ((yearFrom && yearTo && enabled && (yearFrom.value == "" || yearTo.value == "")) ? { error: 'Both starting and ending year are required' } : null);
+ };
+ }
+}