@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.
Files changed (53) hide show
  1. package/lib/bin/process-resources-index.js +0 -0
  2. package/lib/bin/process-resources.d.ts +6 -1
  3. package/lib/bin/process-resources.js +116 -25
  4. package/lib/bin/validate-catalog.js +34 -13
  5. package/lib/bin/validate-resources-text.js +3 -1
  6. package/lib/catalog/tables/taxon.js +4 -0
  7. package/lib/catalog/value.d.ts +1 -0
  8. package/lib/catalog/value.js +1 -0
  9. package/lib/resources/parse-text.js +6 -3
  10. package/lib/taxon-names/index.js +1 -1
  11. package/package.json +15 -13
  12. package/.gitattributes +0 -1
  13. package/.github/workflows/ci.yml +0 -27
  14. package/CHANGELOG.md +0 -390
  15. package/eslint.config.js +0 -34
  16. package/lib/bin/SHEETS.js +0 -0
  17. package/lib/bin/clean-links.d.ts +0 -2
  18. package/lib/bin/clean-links.js +0 -170
  19. package/lib/bin/download-place-shapes.js +0 -188
  20. package/lib/bin/index-place-shapes.js +0 -115
  21. package/lib/bin/process-resources-problems.js +0 -177
  22. package/lib/bin/validate-linked-data.js +0 -0
  23. package/lib/resources/content/clavis.js +0 -10
  24. package/lib/resources/content/index.js +0 -0
  25. package/lib/resources/content/sdd.js +0 -151
  26. package/lib/resources/sdd.js +0 -78
  27. package/src/bin/generate-linked-data.ts +0 -762
  28. package/src/bin/process-resources-index.ts +0 -100
  29. package/src/bin/process-resources.ts +0 -510
  30. package/src/bin/util.ts +0 -74
  31. package/src/bin/validate-catalog.ts +0 -122
  32. package/src/bin/validate-resources-text.ts +0 -25
  33. package/src/catalog/entities.ts +0 -62
  34. package/src/catalog/entity.ts +0 -116
  35. package/src/catalog/index.ts +0 -33
  36. package/src/catalog/tables/author.ts +0 -15
  37. package/src/catalog/tables/index.ts +0 -14
  38. package/src/catalog/tables/place.ts +0 -14
  39. package/src/catalog/tables/publisher.ts +0 -15
  40. package/src/catalog/tables/taxon.ts +0 -17
  41. package/src/catalog/tables/work.ts +0 -65
  42. package/src/catalog/value.ts +0 -51
  43. package/src/csv.ts +0 -33
  44. package/src/index.ts +0 -4
  45. package/src/module.d.ts +0 -148
  46. package/src/resources/diff-resource.ts +0 -226
  47. package/src/resources/index.ts +0 -4
  48. package/src/resources/parse-name.ts +0 -392
  49. package/src/resources/parse-text.ts +0 -408
  50. package/src/resources/resource.ts +0 -10
  51. package/src/taxon-names/index.ts +0 -79
  52. package/test/resources.js +0 -374
  53. 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))
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
@@ -1,4 +0,0 @@
1
- export * as catalog from './catalog/index'
2
- export * as resources from './resources/index'
3
- export * as taxonNames from './taxon-names/index'
4
- export * as csv from './csv'