@larsgw/formica 0.9.2 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bin/process-resources-index.js +0 -0
- package/lib/bin/process-resources.d.ts +6 -1
- package/lib/bin/process-resources.js +116 -25
- package/lib/bin/validate-catalog.js +34 -13
- package/lib/bin/validate-resources-text.js +3 -1
- package/lib/catalog/tables/taxon.js +4 -0
- package/lib/catalog/value.d.ts +1 -0
- package/lib/catalog/value.js +1 -0
- package/lib/resources/parse-text.js +6 -3
- package/lib/taxon-names/index.js +1 -1
- package/package.json +15 -13
- package/.gitattributes +0 -1
- package/.github/workflows/ci.yml +0 -27
- package/CHANGELOG.md +0 -390
- package/eslint.config.js +0 -34
- package/lib/bin/SHEETS.js +0 -0
- package/lib/bin/clean-links.d.ts +0 -2
- package/lib/bin/clean-links.js +0 -170
- package/lib/bin/download-place-shapes.js +0 -188
- package/lib/bin/index-place-shapes.js +0 -115
- package/lib/bin/process-resources-problems.js +0 -177
- package/lib/bin/validate-linked-data.js +0 -0
- package/lib/resources/content/clavis.js +0 -10
- package/lib/resources/content/index.js +0 -0
- package/lib/resources/content/sdd.js +0 -151
- package/lib/resources/sdd.js +0 -78
- package/src/bin/generate-linked-data.ts +0 -762
- package/src/bin/process-resources-index.ts +0 -100
- package/src/bin/process-resources.ts +0 -510
- package/src/bin/util.ts +0 -74
- package/src/bin/validate-catalog.ts +0 -122
- package/src/bin/validate-resources-text.ts +0 -25
- package/src/catalog/entities.ts +0 -62
- package/src/catalog/entity.ts +0 -116
- package/src/catalog/index.ts +0 -33
- package/src/catalog/tables/author.ts +0 -15
- package/src/catalog/tables/index.ts +0 -14
- package/src/catalog/tables/place.ts +0 -14
- package/src/catalog/tables/publisher.ts +0 -15
- package/src/catalog/tables/taxon.ts +0 -17
- package/src/catalog/tables/work.ts +0 -65
- package/src/catalog/value.ts +0 -51
- package/src/csv.ts +0 -33
- package/src/index.ts +0 -4
- package/src/module.d.ts +0 -148
- package/src/resources/diff-resource.ts +0 -226
- package/src/resources/index.ts +0 -4
- package/src/resources/parse-name.ts +0 -392
- package/src/resources/parse-text.ts +0 -408
- package/src/resources/resource.ts +0 -10
- package/src/taxon-names/index.ts +0 -79
- package/test/resources.js +0 -374
- package/tsconfig.json +0 -15
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { promises as fs } from 'fs'
|
|
4
|
-
import * as path from 'path'
|
|
5
|
-
import { catalog } from '../index'
|
|
6
|
-
|
|
7
|
-
class TaxaValidator {
|
|
8
|
-
taxa: catalog.Entity[];
|
|
9
|
-
errors: WorkError[];
|
|
10
|
-
parentIndex: Record<string, string>;
|
|
11
|
-
|
|
12
|
-
constructor (taxa: catalog.Entities) {
|
|
13
|
-
this.taxa = Array.from(taxa).filter(taxon => taxon.has('ancestors_gbif'))
|
|
14
|
-
this.errors = []
|
|
15
|
-
this.parentIndex = {}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
validate (): WorkError[] {
|
|
19
|
-
for (const taxon of this.taxa) {
|
|
20
|
-
const parents = taxon.get('ancestors_gbif') as string[]
|
|
21
|
-
|
|
22
|
-
if (parents.length > 1) {
|
|
23
|
-
const id = taxon.get('id') as string
|
|
24
|
-
|
|
25
|
-
for (let i = 1; i < parents.length; i++) {
|
|
26
|
-
this.addToIndex(id, 'ancestors_gbif', parents[i], parents[i - 1])
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
for (const taxon of this.taxa) {
|
|
32
|
-
if (taxon.has('gbif')) {
|
|
33
|
-
const id = taxon.get('id') as string
|
|
34
|
-
const parents = taxon.get('ancestors_gbif') as string[]
|
|
35
|
-
const parent = parents[parents.length - 1] as string
|
|
36
|
-
|
|
37
|
-
this.addToIndex(id, 'gbif', taxon.get('gbif') as string, parent)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
for (const taxon of this.taxa) {
|
|
42
|
-
if (taxon.has('children_gbif')) {
|
|
43
|
-
const id = taxon.get('id') as string
|
|
44
|
-
const parents = taxon.get('ancestors_gbif') as string[]
|
|
45
|
-
const parent = parents[parents.length - 1] as string
|
|
46
|
-
|
|
47
|
-
const childIds = taxon.get('children_gbif') as string[]
|
|
48
|
-
for (const childId of childIds) {
|
|
49
|
-
this.addToIndex(id, 'gbif', childId, parent)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return this.errors
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
addToIndex (entity: string, field: string, childId: string, parentId: string) {
|
|
58
|
-
const actualParentId = this.parentIndex[childId]
|
|
59
|
-
|
|
60
|
-
if (!actualParentId) {
|
|
61
|
-
this.parentIndex[childId] = parentId
|
|
62
|
-
} else if (actualParentId !== parentId) {
|
|
63
|
-
const error = `Inconsistent ancestor of ${childId}, expected ${actualParentId} but got ${parentId}`
|
|
64
|
-
this.errors.push({ entity, field, error })
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function validateFile (arg: string): Promise<WorkError[]> {
|
|
70
|
-
const filePath = path.resolve(arg)
|
|
71
|
-
const file = await fs.readFile(filePath, 'utf8')
|
|
72
|
-
const sheet = path.basename(filePath, '.csv')
|
|
73
|
-
const entities = catalog.loadData(file, sheet)
|
|
74
|
-
const errors = entities.validate()
|
|
75
|
-
|
|
76
|
-
const ids = new Set()
|
|
77
|
-
for (const entity of entities) {
|
|
78
|
-
const id = entity.get('id') as string
|
|
79
|
-
|
|
80
|
-
if (ids.has(id)) {
|
|
81
|
-
errors.push({
|
|
82
|
-
entity: id,
|
|
83
|
-
field: 'id',
|
|
84
|
-
error: `ID "${id}" repeated`
|
|
85
|
-
})
|
|
86
|
-
} else {
|
|
87
|
-
ids.add(id)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (sheet === 'taxa') {
|
|
92
|
-
const validator = new TaxaValidator(entities)
|
|
93
|
-
errors.push(...validator.validate())
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return errors
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function main (args: string[]): Promise<void> {
|
|
100
|
-
let exitStatus = 0
|
|
101
|
-
|
|
102
|
-
const results = await Promise.allSettled(args.map(validateFile))
|
|
103
|
-
for (let i = 0; i < results.length; i++) {
|
|
104
|
-
const result = results[i]
|
|
105
|
-
|
|
106
|
-
if (result.status === 'rejected') {
|
|
107
|
-
console.error(`${args[i]}:`)
|
|
108
|
-
console.error(result.reason)
|
|
109
|
-
console.error()
|
|
110
|
-
exitStatus = 1
|
|
111
|
-
} else if (result.value.length > 0) {
|
|
112
|
-
console.error(`${args[i]}:`)
|
|
113
|
-
console.table(result.value)
|
|
114
|
-
console.error()
|
|
115
|
-
exitStatus = 1
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
process.exit(exitStatus)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
main(process.argv.slice(2))
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { promises as fs } from 'fs'
|
|
4
|
-
import * as path from 'path'
|
|
5
|
-
import { resources } from '../index'
|
|
6
|
-
|
|
7
|
-
async function main (args: string[]): Promise<void> {
|
|
8
|
-
let exitStatus = 0
|
|
9
|
-
|
|
10
|
-
for (const arg of args) {
|
|
11
|
-
const filePath = path.resolve(arg)
|
|
12
|
-
const file = await fs.readFile(filePath, 'utf8')
|
|
13
|
-
const id = path.basename(filePath, '.txt')
|
|
14
|
-
try {
|
|
15
|
-
resources.parseTextFile(file, id)
|
|
16
|
-
} catch (error) {
|
|
17
|
-
console.error(filePath + '\n' + error.message.replace(/^/gm, ' ') + '\n')
|
|
18
|
-
exitStatus = 1
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
process.exit(exitStatus)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
main(process.argv.slice(2))
|
package/src/catalog/entities.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Entity } from './entity'
|
|
2
|
-
|
|
3
|
-
export class Entities {
|
|
4
|
-
entities: Entity[];
|
|
5
|
-
indexField: string;
|
|
6
|
-
index: Record<string, Entity>;
|
|
7
|
-
|
|
8
|
-
constructor (entities: Entity[], indexField: string) {
|
|
9
|
-
this.entities = entities
|
|
10
|
-
this.indexField = indexField
|
|
11
|
-
this.index = {}
|
|
12
|
-
for (const entity of this.entities) {
|
|
13
|
-
const key = entity.get(indexField)
|
|
14
|
-
if (typeof key !== 'string') {
|
|
15
|
-
throw new TypeError('Entity missing index field')
|
|
16
|
-
}
|
|
17
|
-
this.index[key] = entity
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
*[Symbol.iterator] () {
|
|
22
|
-
for (const entity of this.entities) {
|
|
23
|
-
yield entity
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
get (key: string): Entity {
|
|
28
|
-
return this.index[key]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
has (key: string): boolean {
|
|
32
|
-
return key in this.index
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
validate (): WorkError[] {
|
|
36
|
-
const errors: WorkError[] = []
|
|
37
|
-
for (const entity of this.entities) {
|
|
38
|
-
for (const { field, error } of entity.validate()) {
|
|
39
|
-
errors.push({
|
|
40
|
-
entity: (entity.get(this.indexField) || '[missing]') as string,
|
|
41
|
-
field,
|
|
42
|
-
error
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return errors
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
toTable (): string[][] {
|
|
50
|
-
if (this.entities.length === 0) {
|
|
51
|
-
return [[]]
|
|
52
|
-
}
|
|
53
|
-
const header = Object.keys(this.entities[0].schema)
|
|
54
|
-
const table = this.entities.map((entity: Entity) => {
|
|
55
|
-
return header.map((field: string): string => {
|
|
56
|
-
const value = entity.get(field) || ''
|
|
57
|
-
return Array.isArray(value) ? value.join('; ') : value
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
return [header, ...table]
|
|
61
|
-
}
|
|
62
|
-
}
|
package/src/catalog/entity.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { parseValue } from './value'
|
|
2
|
-
|
|
3
|
-
export class Entity {
|
|
4
|
-
fields: Record<string, Value>;
|
|
5
|
-
schema: Schema;
|
|
6
|
-
derivedFields: Record<string, Value>;
|
|
7
|
-
|
|
8
|
-
constructor (values: Record<string, string>, schema: Schema) {
|
|
9
|
-
this.fields = {}
|
|
10
|
-
for (const key in values) {
|
|
11
|
-
const value = parseValue(values[key], !!schema[key] && !!schema[key].multiple)
|
|
12
|
-
if (value !== null) {
|
|
13
|
-
this.fields[key] = value
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
this.schema = schema
|
|
18
|
-
this.derivedFields = {}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
deriveFields () {
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
get (key: string): Value | undefined {
|
|
25
|
-
if (key in this.fields) {
|
|
26
|
-
return this.fields[key]
|
|
27
|
-
} else if (key in this.derivedFields) {
|
|
28
|
-
return this.derivedFields[key]
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
has (key: string): boolean {
|
|
33
|
-
return key in this.fields || key in this.derivedFields
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
validate (): FieldError[] {
|
|
37
|
-
const errors = []
|
|
38
|
-
for (const field in { ...this.fields, ...this.schema }) {
|
|
39
|
-
for (const error of this._validateField(field)) {
|
|
40
|
-
errors.push({ field, error })
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return errors
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
_validateField (field: string) {
|
|
47
|
-
const errors = []
|
|
48
|
-
|
|
49
|
-
if (!(field in this.schema)) {
|
|
50
|
-
errors.push('Field unknown')
|
|
51
|
-
return errors
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!(field in this.fields)) {
|
|
55
|
-
if (this.schema[field].required) {
|
|
56
|
-
errors.push('Value(s) required but missing')
|
|
57
|
-
}
|
|
58
|
-
return errors
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const valueCountError = this._validateValueCount(field, this.fields[field])
|
|
62
|
-
if (valueCountError !== undefined) {
|
|
63
|
-
errors.push(valueCountError)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let values = this.fields[field]
|
|
67
|
-
if (!Array.isArray(values)) {
|
|
68
|
-
values = [values]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for (const value of values) {
|
|
72
|
-
const result = this._validateSingleValue(field, value)
|
|
73
|
-
if (result !== undefined) {
|
|
74
|
-
errors.push(result)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return errors
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
_validateValueCount (field: string, value: Value): string | undefined {
|
|
82
|
-
const multipleConfig = this.schema[field].multiple
|
|
83
|
-
const allowMultiple = typeof multipleConfig === 'function'
|
|
84
|
-
? multipleConfig.call(null, this.fields)
|
|
85
|
-
: multipleConfig
|
|
86
|
-
|
|
87
|
-
if (allowMultiple === false) {
|
|
88
|
-
let multiple = false
|
|
89
|
-
|
|
90
|
-
if (Array.isArray(value) && value.length > 2) {
|
|
91
|
-
multiple = true
|
|
92
|
-
} else if (typeof value === 'string' && value.includes('; ')) {
|
|
93
|
-
multiple = true
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (multiple) {
|
|
97
|
-
return 'Multiple values but only one expected'
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return undefined
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
_validateSingleValue (field: string, value: SingleValue): string | undefined {
|
|
105
|
-
const config = this.schema[field].format
|
|
106
|
-
if (config === undefined) {
|
|
107
|
-
return undefined
|
|
108
|
-
} else if (Array.isArray(config) && (value === null || !config.includes(value))) {
|
|
109
|
-
return `The value "${value}" is not included: ${config.join(', ')}`
|
|
110
|
-
} else if (config instanceof RegExp && !config.test(value)) {
|
|
111
|
-
return `The value "${value}" does not conform to pattern: ${config.source}`
|
|
112
|
-
} else if (typeof config === 'function' && !config.call(null, value)) {
|
|
113
|
-
return `The value "${value}" does not conform to pattern: ${config.name}`
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/catalog/index.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Entities } from './entities'
|
|
2
|
-
import { Entity } from './entity'
|
|
3
|
-
import { TYPE_INFO } from './tables/index'
|
|
4
|
-
import { parseCsv } from '../csv'
|
|
5
|
-
|
|
6
|
-
function getTypeInfo (type: string): [typeof Entity, string] {
|
|
7
|
-
switch (type) {
|
|
8
|
-
case 'authors': return TYPE_INFO.authors
|
|
9
|
-
case 'places': return TYPE_INFO.places
|
|
10
|
-
case 'publishers': return TYPE_INFO.publishers
|
|
11
|
-
case 'taxa': return TYPE_INFO.taxa
|
|
12
|
-
case 'catalog': return TYPE_INFO.catalog
|
|
13
|
-
default: throw new TypeError(`Unknown type "${type}"`)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export { Entities, Entity, getTypeInfo }
|
|
18
|
-
|
|
19
|
-
export function loadData (file: string, type: string): Entities {
|
|
20
|
-
const [subClass, indexField] = getTypeInfo(type)
|
|
21
|
-
const entities = []
|
|
22
|
-
const [header, ...rows] = parseCsv(file)
|
|
23
|
-
|
|
24
|
-
for (const row of rows) {
|
|
25
|
-
const data: Record<string, string> = {}
|
|
26
|
-
for (let index = 0; index < header.length && index < row.length; index++) {
|
|
27
|
-
data[header[index]] = row[index]
|
|
28
|
-
}
|
|
29
|
-
entities.push(new subClass(data, {}))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return new Entities(entities, indexField)
|
|
33
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { FORMATS } from '../value'
|
|
2
|
-
import { Entity } from '../entity'
|
|
3
|
-
|
|
4
|
-
export class Author extends Entity {
|
|
5
|
-
constructor (values: Record<string, string>) {
|
|
6
|
-
super(values, {
|
|
7
|
-
name: { required: true, multiple: true },
|
|
8
|
-
id: { required: true, multiple: false, format: FORMATS.AUTHOR_ID },
|
|
9
|
-
qid: { required: false, multiple: false, format: FORMATS.QID },
|
|
10
|
-
display_name: { required: true, multiple: false },
|
|
11
|
-
full_names: { required: false, multiple: true },
|
|
12
|
-
duplicate_of: { required: false, multiple: false, format: FORMATS.AUTHOR_ID }
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Entity } from '../entity'
|
|
2
|
-
import { Author } from './author'
|
|
3
|
-
import { Publisher } from './publisher'
|
|
4
|
-
import { Place } from './place'
|
|
5
|
-
import { Taxon } from './taxon'
|
|
6
|
-
import { Work } from './work'
|
|
7
|
-
|
|
8
|
-
export const TYPE_INFO: Record<string, [typeof Entity, string]> = {
|
|
9
|
-
authors: [Author, 'id'],
|
|
10
|
-
publishers: [Publisher, 'id'],
|
|
11
|
-
places: [Place, 'id'],
|
|
12
|
-
taxa: [Taxon, 'id'],
|
|
13
|
-
catalog: [Work, 'id']
|
|
14
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { FORMATS } from '../value'
|
|
2
|
-
import { Entity } from '../entity'
|
|
3
|
-
|
|
4
|
-
export class Place extends Entity {
|
|
5
|
-
constructor (values: Record<string, string>) {
|
|
6
|
-
super(values, {
|
|
7
|
-
name: { required: true, multiple: false },
|
|
8
|
-
id: { required: true, multiple: false, format: FORMATS.PLACE_ID },
|
|
9
|
-
qid: { required: false, multiple: false, format: FORMATS.QID },
|
|
10
|
-
display_name: { required: true, multiple: false },
|
|
11
|
-
duplicate_of: { required: false, multiple: false, format: FORMATS.PLACE_ID }
|
|
12
|
-
})
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { FORMATS } from '../value'
|
|
2
|
-
import { Entity } from '../entity'
|
|
3
|
-
|
|
4
|
-
export class Publisher extends Entity {
|
|
5
|
-
constructor (values: Record<string, string>) {
|
|
6
|
-
super(values, {
|
|
7
|
-
name: { required: true, multiple: true },
|
|
8
|
-
id: { required: true, multiple: false, format: FORMATS.PUBLISHER_ID },
|
|
9
|
-
qid: { required: false, multiple: false, format: FORMATS.QID },
|
|
10
|
-
display_name: { required: true, multiple: false },
|
|
11
|
-
full_names: { required: false, multiple: true },
|
|
12
|
-
duplicate_of: { required: false, multiple: false, format: FORMATS.PUBLISHER_ID }
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { FORMATS } from '../value'
|
|
2
|
-
import { Entity } from '../entity'
|
|
3
|
-
|
|
4
|
-
export class Taxon extends Entity {
|
|
5
|
-
constructor (values: Record<string, string>) {
|
|
6
|
-
super(values, {
|
|
7
|
-
name: { required: true, multiple: false },
|
|
8
|
-
id: { required: true, multiple: false, format: FORMATS.TAXON_ID },
|
|
9
|
-
qid: { required: false, multiple: false, format: FORMATS.QID },
|
|
10
|
-
rank: { required: false, multiple: false },
|
|
11
|
-
gbif: { required: false, multiple: false, format: FORMATS.INTEGER },
|
|
12
|
-
children_gbif: { required: false, multiple: true, format: FORMATS.INTEGER },
|
|
13
|
-
ancestors_gbif: { required: false, multiple: true, format: FORMATS.INTEGER },
|
|
14
|
-
duplicate_of: { required: false, multiple: false, format: FORMATS.TAXON_ID }
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { FORMATS, CHECK } from '../value'
|
|
2
|
-
import { Entity } from '../entity'
|
|
3
|
-
|
|
4
|
-
export class Work extends Entity {
|
|
5
|
-
constructor (values: Record<string, string>) {
|
|
6
|
-
super(values, {
|
|
7
|
-
id: { required: true, multiple: false, format: FORMATS.WORK_ID },
|
|
8
|
-
title: { required: true, multiple: CHECK.MULTILANG },
|
|
9
|
-
author: { required: false, multiple: true },
|
|
10
|
-
url: { required: false, multiple: true, format: FORMATS.URL },
|
|
11
|
-
fulltext_url: { required: false, multiple: true, format: FORMATS.URL },
|
|
12
|
-
archive_url: { required: false, multiple: true, format: FORMATS.URL },
|
|
13
|
-
entry_type: { required: true, multiple: false, format: FORMATS.ENTRY_TYPE },
|
|
14
|
-
date: { required: false, multiple: false, format: FORMATS.EDTF_0 },
|
|
15
|
-
publisher: { required: false, multiple: true },
|
|
16
|
-
series: { required: false, multiple: false },
|
|
17
|
-
ISSN: { required: false, multiple: false, format: FORMATS.ISSN_L },
|
|
18
|
-
ISBN: { required: false, multiple: CHECK.ISBN, format: FORMATS.ISBN },
|
|
19
|
-
DOI: { required: false, multiple: false, format: FORMATS.DOI },
|
|
20
|
-
QID: { required: false, multiple: false, format: FORMATS.QID },
|
|
21
|
-
volume: { required: false, multiple: false },
|
|
22
|
-
issue: { required: false, multiple: false },
|
|
23
|
-
pages: { required: false, multiple: false },
|
|
24
|
-
edition: { required: false, multiple: false },
|
|
25
|
-
language: { required: true, multiple: true, format: FORMATS.LANGUAGE },
|
|
26
|
-
license: { required: false, multiple: true, format: FORMATS.LICENSE },
|
|
27
|
-
key_type: { required: true, multiple: true, format: FORMATS.KEY_TYPE },
|
|
28
|
-
taxon: { required: true, multiple: true },
|
|
29
|
-
taxon_scope: { required: false, multiple: true },
|
|
30
|
-
scope: { required: false, multiple: true },
|
|
31
|
-
region: { required: true, multiple: true },
|
|
32
|
-
complete: { required: false, multiple: false, format: FORMATS.COMPLETE },
|
|
33
|
-
key_characteristics: { required: false, multiple: true },
|
|
34
|
-
target_taxa: { required: false, multiple: true },
|
|
35
|
-
listed_in: { required: false, multiple: true, format: FORMATS.WORK_ID },
|
|
36
|
-
part_of: { required: false, multiple: true, format: FORMATS.WORK_ID },
|
|
37
|
-
version_of: { required: false, multiple: true, format: FORMATS.WORK_ID },
|
|
38
|
-
duplicate_of: { required: false, multiple: false, format: FORMATS.WORK_ID }
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
deriveFields () {
|
|
43
|
-
if (typeof this.fields.date === 'string') {
|
|
44
|
-
const year = parseInt(this.fields.date.split('-')[0])
|
|
45
|
-
this.derivedFields.year = year.toString()
|
|
46
|
-
this.derivedFields.decade = (year - (year % 10)).toString()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (typeof this.fields.license === 'string' && !this.fields.license.endsWith('?>')) {
|
|
50
|
-
this.derivedFields.access = 'Open license'
|
|
51
|
-
} else {
|
|
52
|
-
const info = this.fields.url
|
|
53
|
-
const content = this.fields.fulltext_url
|
|
54
|
-
const archive = this.fields.archive_url
|
|
55
|
-
|
|
56
|
-
if (typeof content === 'string') {
|
|
57
|
-
this.derivedFields.access = 'Full text available, no license'
|
|
58
|
-
} else if (typeof archive === 'string' && (!(typeof info === 'string') || !archive.endsWith(info))) {
|
|
59
|
-
this.derivedFields.access = 'Archived full text available, no license'
|
|
60
|
-
} else {
|
|
61
|
-
this.derivedFields.access = 'No full text available'
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
package/src/catalog/value.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import spdxLicenseList from 'spdx-license-list'
|
|
2
|
-
import ietfTagListFactory from 'ietf-language-tag-regex'
|
|
3
|
-
|
|
4
|
-
const ietfTagList = ietfTagListFactory()
|
|
5
|
-
|
|
6
|
-
export const FORMATS = {
|
|
7
|
-
ENTRY_TYPE: ['print', 'online', 'cd', 'application'],
|
|
8
|
-
KEY_TYPE: ['key', 'matrix', 'reference', 'gallery', 'checklist', 'supplement', 'collection', 'algorithm'],
|
|
9
|
-
COMPLETE: ['TRUE', 'FALSE'],
|
|
10
|
-
|
|
11
|
-
WORK_ID: /^B[1-9]\d*$/,
|
|
12
|
-
AUTHOR_ID: /^A[1-9]\d*$/,
|
|
13
|
-
PLACE_ID: /^G[1-9]\d*$/,
|
|
14
|
-
PUBLISHER_ID: /^P[1-9]\d*$/,
|
|
15
|
-
TAXON_ID: /^T[1-9]\d*$/,
|
|
16
|
-
EDTF_0: /^(\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}:\d{2}(Z|[-+]\d{2}(:\d{2})?))?)?)?|\d{4}(-\d{2}(-\d{2})?)?\/(\d{4}(-\d{2}(-\d{2})?)?|\.\.))$/,
|
|
17
|
-
ISSN_L: /^[0-9]{4}-[0-9]{3}[0-9X]$/,
|
|
18
|
-
ISBN: /^(\d{13}|\d{9}[0-9X])$/,
|
|
19
|
-
DOI: /^10\./,
|
|
20
|
-
QID: /^Q[1-9][0-9]*$/,
|
|
21
|
-
URL: /^(ftp|http|https):\/\/((?:[a-z0-9][a-z0-9-_]*?[a-z0-9]?\.)+(?:xn--)?[a-z0-9]+)(:\d*)?((?:\/(?:%\d\d|[!$&'()*+,\-.0-9";=@A-Z_a-z~])*)*)(\?(?:%\d\d|[!$&'()*+,\-./0-9:;=?@A-Z_a-z~])*)?(#(?:%\d\d|[!$&'()*+,\-./0-9:;=?@A-Z_a-z~])*)?/i,
|
|
22
|
-
INTEGER: /^(0|[1-9]\d*)$/,
|
|
23
|
-
|
|
24
|
-
LICENSE (value: SingleValue) { return /^<(public domain|.+\?)>$/.test(value) || !!spdxLicenseList[value] },
|
|
25
|
-
LANGUAGE (value: SingleValue) { return ietfTagList.test(value) }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const CHECK = {
|
|
29
|
-
MULTILANG (entry: Record<string, Value>) {
|
|
30
|
-
return Array.isArray(entry.language) && entry.language.length > 1
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
ISBN (entry: Record<string, Value>) {
|
|
34
|
-
if (!Array.isArray(entry.ISBN) || entry.ISBN.length < 2) { return false }
|
|
35
|
-
const a = entry.ISBN[0].length === 10
|
|
36
|
-
const b = entry.ISBN[0].length === 13
|
|
37
|
-
const c = entry.ISBN[1].length === 10
|
|
38
|
-
const d = entry.ISBN[1].length === 13
|
|
39
|
-
return (a && d) || (b && c)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function parseValue (value: string, multiple: boolean): Value | null {
|
|
44
|
-
if (value === '') {
|
|
45
|
-
return null
|
|
46
|
-
} else if (multiple) {
|
|
47
|
-
return value.split('; ')
|
|
48
|
-
} else {
|
|
49
|
-
return value
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/csv.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export function parseCsv (file: string): string[][] {
|
|
2
|
-
const values = file
|
|
3
|
-
.trim()
|
|
4
|
-
.match(/("([^"]|"")*?"|[^,\n]+|(?!$))(,|\n|$)/g)
|
|
5
|
-
|
|
6
|
-
if (values === null) {
|
|
7
|
-
throw new TypeError('Failed to parse csv')
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return values.reduce((rows, value) => {
|
|
11
|
-
const last: string[] = rows[rows.length - 1]
|
|
12
|
-
if (value.endsWith('\n')) {
|
|
13
|
-
rows.push([])
|
|
14
|
-
}
|
|
15
|
-
value = value.replace(/[,\n]$/, '')
|
|
16
|
-
last.push(value.startsWith('"') ? value.replace(/""/g, '"').slice(1, -1) : value)
|
|
17
|
-
return rows
|
|
18
|
-
}, [[]])
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function formatCsv (table: string[][], delimiter = ',') {
|
|
22
|
-
return table
|
|
23
|
-
.map((row: string[]) => {
|
|
24
|
-
return row.map(value => {
|
|
25
|
-
if (/["\n]/.test(value) || value.includes(delimiter)) {
|
|
26
|
-
return `"${value.replace(/"/g, '""')}"`
|
|
27
|
-
} else {
|
|
28
|
-
return value
|
|
29
|
-
}
|
|
30
|
-
}).join(delimiter)
|
|
31
|
-
})
|
|
32
|
-
.join('\n') + '\n'
|
|
33
|
-
}
|
package/src/index.ts
DELETED