@ca-plant-list/ca-plant-list 0.3.1 → 0.3.3
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/.vscode/settings.json +5 -0
- package/data/exceptions.json +88 -83
- package/data/glossary/ovary.md +3 -0
- package/data/glossary/pistil.md +3 -0
- package/data/glossary/stigma.md +3 -0
- package/data/glossary/style.md +3 -0
- package/data/illustrations/inkscape/pistil.svg +156 -0
- package/data/synonyms.csv +8 -2
- package/data/taxa.csv +56 -54
- package/data/text/Ceanothus-cuneatus-var-cuneatus.md +1 -0
- package/data/text/Ceanothus-leucodermis.md +1 -0
- package/data/text/Delphinium-californicum-subsp-californicum.md +1 -0
- package/data/text/Delphinium-decorum-subsp-decorum.md +1 -0
- package/data/text/Delphinium-hesperium-subsp-hesperium.md +1 -0
- package/data/text/Delphinium-patens-subsp-patens.md +1 -0
- package/data/text/Galium-andrewsii-subsp-gatense.md +1 -0
- package/data/text/Helianthella-californica-var-californica.md +1 -0
- package/data/text/Helianthella-castanea.md +1 -0
- package/data/text/Iris-macrosiphon.md +1 -0
- package/data/text/Lasthenia-californica-subsp-californica.md +1 -0
- package/data/text/Lasthenia-gracilis.md +1 -0
- package/data/text/Lithophragma-affine.md +1 -1
- package/data/text/Lithophragma-parviflorum-var-parviflorum.md +1 -1
- package/data/text/Lomatium-californicum.md +0 -0
- package/data/text/Lomatium-dasycarpum-subsp-dasycarpum.md +1 -0
- package/data/text/Lomatium-utriculatum.md +1 -0
- package/data/text/Nemophila-heterophylla.md +1 -0
- package/data/text/Nemophila-parviflora-var-parviflora.md +1 -0
- package/data/text/Plectritis-ciliosa.md +1 -0
- package/data/text/Plectritis-macrocera.md +1 -0
- package/data/text/Primula-clevelandii-var-patula.md +1 -0
- package/data/text/Primula-hendersonii.md +1 -0
- package/data/text/Sidalcea-diploscypha.md +1 -0
- package/data/text/Thysanocarpus-curvipes.md +1 -0
- package/data/text/Thysanocarpus-laciniatus.md +1 -0
- package/data/text/Trillium-chloropetalum.md +1 -1
- package/data/text/Viola-douglasii.md +1 -0
- package/data/text/Viola-pedunculata.md +1 -0
- package/data/text/Viola-purpurea-subsp-quercetorum.md +1 -0
- package/ebook/css/main.css +5 -0
- package/lib/basepagerenderer.js +30 -28
- package/lib/csv.js +13 -0
- package/lib/dateutils.js +36 -16
- package/lib/ebook/pages/taxonpage.js +63 -36
- package/lib/errorlog.js +16 -11
- package/lib/exceptions.js +2 -0
- package/lib/families.js +109 -71
- package/lib/files.js +103 -45
- package/lib/generictaxaloader.js +15 -7
- package/lib/html.js +2 -2
- package/lib/index.js +38 -3
- package/lib/taxa.js +175 -86
- package/lib/taxaloader.js +28 -15
- package/lib/taxaprocessor.js +6 -8
- package/lib/taxon.js +109 -56
- package/lib/web/pagetaxon.js +1 -1
- package/package.json +6 -8
- package/scripts/build-ebook.js +30 -25
- package/lib/index.d.ts +0 -327
package/lib/taxa.js
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
import { Config } from "./config.js";
|
2
|
-
import { Taxon } from "./taxon.js";
|
3
2
|
import { HTML } from "./html.js";
|
4
3
|
import { CSV } from "./csv.js";
|
5
4
|
import { RarePlants } from "./rareplants.js";
|
5
|
+
import { ErrorLog, Taxon } from "./index.js";
|
6
6
|
|
7
7
|
const FLOWER_COLORS = [
|
8
8
|
{ name: "white", color: "white" },
|
@@ -11,52 +11,68 @@ const FLOWER_COLORS = [
|
|
11
11
|
{ name: "orange", color: "orange" },
|
12
12
|
{ name: "yellow", color: "yellow" },
|
13
13
|
{ name: "blue", color: "blue" },
|
14
|
+
{ name: "purple", color: "purple" },
|
14
15
|
];
|
15
16
|
|
17
|
+
/**
|
18
|
+
* @type {Object.<string,import("./index.js").TaxaCol>}
|
19
|
+
*/
|
16
20
|
const TAXA_LIST_COLS = {
|
17
21
|
CESA: {
|
18
22
|
title: "California",
|
19
|
-
data: (
|
23
|
+
data: (t) => RarePlants.getCESADescription(t.getCESA()),
|
20
24
|
},
|
21
25
|
COMMON_NAME: {
|
22
26
|
title: "Common Name",
|
23
|
-
data: (
|
27
|
+
data: (t) => t.getCommonNames().join(", "),
|
24
28
|
},
|
25
29
|
CNPS_RANK: {
|
26
30
|
title: "CNPS Rank",
|
27
|
-
data: (
|
31
|
+
data: (t) =>
|
32
|
+
HTML.getToolTip(
|
33
|
+
HTML.textElement("span", t.getRPIRankAndThreat()),
|
34
|
+
t.getRPIRankAndThreatTooltip()
|
35
|
+
),
|
28
36
|
},
|
29
37
|
FESA: {
|
30
38
|
title: "Federal",
|
31
|
-
data: (
|
39
|
+
data: (t) => RarePlants.getFESADescription(t.getFESA()),
|
32
40
|
},
|
33
41
|
SPECIES: {
|
34
42
|
title: "Species",
|
35
|
-
data: (
|
43
|
+
data: (t) => t.getHTMLLink(true, true),
|
36
44
|
},
|
37
45
|
SPECIES_BARE: {
|
38
46
|
title: "Species",
|
39
|
-
data: (
|
47
|
+
data: (t) => t.getHTMLLink(true, false),
|
40
48
|
},
|
41
49
|
};
|
42
50
|
|
43
|
-
const DEFAULT_COLUMNS = [
|
51
|
+
const DEFAULT_COLUMNS = [TAXA_LIST_COLS.SPECIES, TAXA_LIST_COLS.COMMON_NAME];
|
44
52
|
|
45
53
|
class FlowerColor {
|
46
|
-
|
47
54
|
#color;
|
55
|
+
/** @type {Taxon[]} */
|
48
56
|
#taxa = [];
|
49
57
|
|
50
|
-
|
58
|
+
/**
|
59
|
+
* @param {string} color
|
60
|
+
*/
|
61
|
+
constructor(color) {
|
51
62
|
this.#color = color;
|
52
63
|
}
|
53
64
|
|
54
|
-
|
55
|
-
|
65
|
+
/**
|
66
|
+
* @param {Taxon} taxon
|
67
|
+
*/
|
68
|
+
addTaxon(taxon) {
|
69
|
+
this.#taxa.push(taxon);
|
56
70
|
}
|
57
71
|
|
58
|
-
getColorName(
|
59
|
-
return uc
|
72
|
+
getColorName(uc = false) {
|
73
|
+
return uc
|
74
|
+
? this.#color[0].toUpperCase() + this.#color.substring(1)
|
75
|
+
: this.#color;
|
60
76
|
}
|
61
77
|
|
62
78
|
getFileName() {
|
@@ -66,64 +82,100 @@ class FlowerColor {
|
|
66
82
|
getTaxa() {
|
67
83
|
return this.#taxa;
|
68
84
|
}
|
69
|
-
|
70
85
|
}
|
71
86
|
|
72
87
|
class Taxa {
|
73
|
-
|
74
88
|
#errorLog;
|
89
|
+
/** @type {Object<string,Taxon>} */
|
75
90
|
#taxa = {};
|
91
|
+
/** @type {Object<string,FlowerColor>} */
|
76
92
|
#flower_colors = {};
|
77
93
|
#sortedTaxa;
|
78
94
|
#synonyms = new Set();
|
79
|
-
|
80
|
-
|
95
|
+
#isSubset;
|
96
|
+
|
97
|
+
/**
|
98
|
+
*
|
99
|
+
* @param {*} inclusionList
|
100
|
+
* @param {ErrorLog} errorLog
|
101
|
+
* @param {*} showFlowerErrors
|
102
|
+
* @param {*} taxaMeta
|
103
|
+
* @param {*} taxonClass
|
104
|
+
* @param {*} extraTaxa
|
105
|
+
* @param {*} extraSynonyms
|
106
|
+
*/
|
107
|
+
constructor(
|
108
|
+
inclusionList,
|
109
|
+
errorLog,
|
110
|
+
showFlowerErrors,
|
111
|
+
taxaMeta = {},
|
112
|
+
taxonClass = Taxon,
|
113
|
+
extraTaxa = [],
|
114
|
+
extraSynonyms = []
|
115
|
+
) {
|
116
|
+
this.#isSubset = inclusionList !== true;
|
81
117
|
|
82
118
|
this.#errorLog = errorLog;
|
83
119
|
|
84
|
-
for (
|
85
|
-
this.#flower_colors[
|
120
|
+
for (const color of FLOWER_COLORS) {
|
121
|
+
this.#flower_colors[color.name] = new FlowerColor(color.name);
|
86
122
|
}
|
87
123
|
|
88
124
|
const dataDir = Config.getPackageDir() + "/data";
|
89
125
|
|
90
|
-
const taxaCSV = CSV.parseFile(
|
91
|
-
this.#loadTaxa(
|
92
|
-
|
126
|
+
const taxaCSV = CSV.parseFile(dataDir, "taxa.csv");
|
127
|
+
this.#loadTaxa(
|
128
|
+
taxaCSV,
|
129
|
+
inclusionList,
|
130
|
+
taxaMeta,
|
131
|
+
taxonClass,
|
132
|
+
showFlowerErrors
|
133
|
+
);
|
134
|
+
this.#loadTaxa(
|
135
|
+
extraTaxa,
|
136
|
+
inclusionList,
|
137
|
+
taxaMeta,
|
138
|
+
taxonClass,
|
139
|
+
showFlowerErrors
|
140
|
+
);
|
93
141
|
|
94
142
|
// Make sure everything in the inclusionList has been loaded.
|
95
|
-
for (
|
96
|
-
if (
|
97
|
-
this.#errorLog.log(
|
143
|
+
for (const name of Object.keys(inclusionList)) {
|
144
|
+
if (!this.getTaxon(name)) {
|
145
|
+
this.#errorLog.log(name, "not found in taxon list");
|
98
146
|
}
|
99
147
|
}
|
100
148
|
|
101
|
-
this.#sortedTaxa = Object.values(
|
102
|
-
|
103
|
-
|
104
|
-
this.#loadSyns( synCSV, inclusionList );
|
105
|
-
this.#loadSyns( extraSynonyms, inclusionList );
|
149
|
+
this.#sortedTaxa = Object.values(this.#taxa).sort((a, b) =>
|
150
|
+
a.getName().localeCompare(b.getName())
|
151
|
+
);
|
106
152
|
|
153
|
+
const synCSV = CSV.parseFile(dataDir, "synonyms.csv");
|
154
|
+
this.#loadSyns(synCSV, inclusionList);
|
155
|
+
this.#loadSyns(extraSynonyms, inclusionList);
|
107
156
|
}
|
108
157
|
|
109
|
-
|
110
|
-
|
158
|
+
/**
|
159
|
+
* @param {Taxon[]} taxa
|
160
|
+
* @param {import("./index.js").TaxaCol[]} columns
|
161
|
+
*/
|
162
|
+
static getHTMLTable(taxa, columns = DEFAULT_COLUMNS) {
|
111
163
|
let html = "<table><thead>";
|
112
|
-
for (
|
164
|
+
for (const col of columns) {
|
113
165
|
const className = col.class;
|
114
166
|
const atts = className ? { class: className } : {};
|
115
|
-
html += HTML.textElement(
|
167
|
+
html += HTML.textElement("th", col.title, atts);
|
116
168
|
}
|
117
169
|
html += "</thead>";
|
118
170
|
html += "<tbody>";
|
119
171
|
|
120
|
-
for (
|
172
|
+
for (const taxon of taxa) {
|
121
173
|
html += "<tr>";
|
122
|
-
for (
|
123
|
-
const data = col.data(
|
174
|
+
for (const col of columns) {
|
175
|
+
const data = col.data(taxon);
|
124
176
|
const className = col.class;
|
125
177
|
const atts = className ? { class: className } : {};
|
126
|
-
html += HTML.wrap(
|
178
|
+
html += HTML.wrap("td", data, atts);
|
127
179
|
}
|
128
180
|
html += "</tr>";
|
129
181
|
}
|
@@ -134,99 +186,136 @@ class Taxa {
|
|
134
186
|
return html;
|
135
187
|
}
|
136
188
|
|
137
|
-
|
138
|
-
|
189
|
+
/**
|
190
|
+
* @param {string} name
|
191
|
+
*/
|
192
|
+
getFlowerColor(name) {
|
193
|
+
return this.#flower_colors[name];
|
139
194
|
}
|
140
195
|
|
141
196
|
static getFlowerColorNames() {
|
142
|
-
return FLOWER_COLORS.map(
|
197
|
+
return FLOWER_COLORS.map((o) => o.name);
|
143
198
|
}
|
144
199
|
|
145
200
|
static getFlowerColors() {
|
146
201
|
return FLOWER_COLORS;
|
147
202
|
}
|
148
203
|
|
149
|
-
|
150
|
-
|
204
|
+
/**
|
205
|
+
* @param {string} name
|
206
|
+
*/
|
207
|
+
getTaxon(name) {
|
208
|
+
return this.#taxa[name];
|
151
209
|
}
|
152
210
|
|
153
211
|
getTaxonList() {
|
154
212
|
return this.#sortedTaxa;
|
155
213
|
}
|
156
214
|
|
157
|
-
|
158
|
-
|
215
|
+
/**
|
216
|
+
* @param {string} formerName
|
217
|
+
*/
|
218
|
+
hasSynonym(formerName) {
|
219
|
+
return this.#synonyms.has(formerName);
|
159
220
|
}
|
160
221
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
222
|
+
/**
|
223
|
+
* true if an inclusion list was supplied when reading the taxa.
|
224
|
+
* @returns {boolean}
|
225
|
+
*/
|
226
|
+
isSubset() {
|
227
|
+
return this.#isSubset;
|
228
|
+
}
|
229
|
+
|
230
|
+
/**
|
231
|
+
* @param {*} synCSV
|
232
|
+
* @param {*} inclusionList
|
233
|
+
*/
|
234
|
+
#loadSyns(synCSV, inclusionList) {
|
235
|
+
for (const syn of synCSV) {
|
236
|
+
const currName = syn["Current"];
|
237
|
+
const taxon = this.getTaxon(currName);
|
238
|
+
if (!taxon) {
|
239
|
+
if (inclusionList === true && !syn.Type) {
|
167
240
|
// If including all taxa, note the error - the target is not defined, and this is not
|
168
241
|
// a synonym for a non-Jepson system.
|
169
|
-
console.log(
|
242
|
+
console.log("synonym target not found: " + currName);
|
170
243
|
}
|
171
244
|
continue;
|
172
245
|
}
|
173
|
-
const formerName = syn[
|
174
|
-
this.#synonyms.add(
|
175
|
-
taxon.addSynonym(
|
246
|
+
const formerName = syn["Former"];
|
247
|
+
this.#synonyms.add(formerName);
|
248
|
+
taxon.addSynonym(formerName, syn["Type"]);
|
176
249
|
}
|
177
250
|
}
|
178
251
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
252
|
+
/**
|
253
|
+
* @param {*} taxaCSV
|
254
|
+
* @param {*} inclusionList
|
255
|
+
* @param {*} taxaMeta
|
256
|
+
* @param {Taxon} taxonClass
|
257
|
+
* @param {boolean} showFlowerErrors
|
258
|
+
*/
|
259
|
+
#loadTaxa(taxaCSV, inclusionList, taxaMeta, taxonClass, showFlowerErrors) {
|
260
|
+
for (const row of taxaCSV) {
|
261
|
+
const name = row["taxon_name"];
|
183
262
|
|
184
263
|
let taxon_overrides = {};
|
185
|
-
if (
|
186
|
-
taxon_overrides = inclusionList[
|
187
|
-
if (
|
264
|
+
if (inclusionList !== true) {
|
265
|
+
taxon_overrides = inclusionList[name];
|
266
|
+
if (!taxon_overrides) {
|
188
267
|
continue;
|
189
268
|
}
|
190
269
|
}
|
191
270
|
|
192
|
-
if (
|
193
|
-
this.#errorLog.log(
|
271
|
+
if (this.#taxa[name]) {
|
272
|
+
this.#errorLog.log(name, "has multiple entries");
|
194
273
|
}
|
195
274
|
|
196
|
-
const status = taxon_overrides[
|
197
|
-
if (
|
198
|
-
row[
|
275
|
+
const status = taxon_overrides["status"];
|
276
|
+
if (status !== undefined) {
|
277
|
+
row["status"] = status;
|
199
278
|
}
|
200
|
-
const taxon = new taxonClass(
|
201
|
-
this.#taxa[
|
279
|
+
const taxon = new taxonClass(row, taxaMeta[name]);
|
280
|
+
this.#taxa[name] = taxon;
|
202
281
|
const colors = taxon.getFlowerColors();
|
203
|
-
if (
|
204
|
-
for (
|
205
|
-
const color = this.#flower_colors[
|
206
|
-
if (
|
207
|
-
throw new Error(
|
282
|
+
if (colors) {
|
283
|
+
for (const colorName of colors) {
|
284
|
+
const color = this.#flower_colors[colorName];
|
285
|
+
if (!color) {
|
286
|
+
throw new Error(
|
287
|
+
'flower color "' + colorName + '" not found'
|
288
|
+
);
|
208
289
|
}
|
209
|
-
color.addTaxon(
|
290
|
+
color.addTaxon(taxon);
|
210
291
|
}
|
211
292
|
}
|
212
293
|
|
213
|
-
if (
|
294
|
+
if (showFlowerErrors) {
|
214
295
|
// Make sure flower colors/bloom times are present or not depending on taxon.
|
215
|
-
if (
|
216
|
-
if (
|
217
|
-
|
296
|
+
if (taxon.shouldHaveFlowers()) {
|
297
|
+
if (
|
298
|
+
!colors ||
|
299
|
+
!taxon.getBloomStart() ||
|
300
|
+
!taxon.getBloomEnd()
|
301
|
+
) {
|
302
|
+
this.#errorLog.log(
|
303
|
+
name,
|
304
|
+
"does not have all flower info"
|
305
|
+
);
|
218
306
|
}
|
219
307
|
} else {
|
220
|
-
if (
|
221
|
-
|
308
|
+
if (
|
309
|
+
colors ||
|
310
|
+
taxon.getBloomStart() ||
|
311
|
+
taxon.getBloomEnd()
|
312
|
+
) {
|
313
|
+
this.#errorLog.log(name, "should not have flower info");
|
222
314
|
}
|
223
315
|
}
|
224
316
|
}
|
225
317
|
}
|
226
|
-
|
227
318
|
}
|
228
|
-
|
229
|
-
|
230
319
|
}
|
231
320
|
|
232
|
-
export { Taxa, TAXA_LIST_COLS };
|
321
|
+
export { Taxa, TAXA_LIST_COLS };
|
package/lib/taxaloader.js
CHANGED
@@ -1,35 +1,48 @@
|
|
1
|
-
import { CSV } from "./csv.js";
|
2
|
-
import { Files } from "./files.js";
|
3
1
|
import { GenericTaxaLoader } from "./generictaxaloader.js";
|
4
|
-
import { Taxa } from "./
|
2
|
+
import { CSV, Files, Taxa } from "./index.js";
|
5
3
|
|
6
4
|
class TaxaLoader extends GenericTaxaLoader {
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
/**
|
6
|
+
* @param {*} options
|
7
|
+
*/
|
8
|
+
constructor(options) {
|
9
|
+
super(options);
|
10
10
|
}
|
11
11
|
|
12
|
+
/**
|
13
|
+
* @return {Promise<Taxa>}
|
14
|
+
*/
|
12
15
|
async loadTaxa() {
|
13
|
-
|
16
|
+
/**
|
17
|
+
*
|
18
|
+
* @param {string} dataDir
|
19
|
+
* @returns
|
20
|
+
*/
|
21
|
+
function getIncludeList(dataDir) {
|
14
22
|
// Read inclusion list.
|
15
23
|
const includeFileName = "taxa_include.csv";
|
16
24
|
const includeFilePath = dataDir + "/" + includeFileName;
|
17
|
-
if (
|
18
|
-
console.log(
|
25
|
+
if (!Files.exists(includeFilePath)) {
|
26
|
+
console.log(includeFilePath + " not found; loading all taxa");
|
19
27
|
return true;
|
20
28
|
}
|
21
|
-
|
29
|
+
/**@type { import("./index.js").TaxonData[]} */
|
30
|
+
const includeCSV = CSV.parseFile(dataDir, includeFileName);
|
31
|
+
/** @type {Object<string,import("./index.js").TaxonData>} */
|
22
32
|
const include = {};
|
23
|
-
for (
|
24
|
-
include[
|
33
|
+
for (const row of includeCSV) {
|
34
|
+
include[row["taxon_name"]] = row;
|
25
35
|
}
|
26
36
|
return include;
|
27
37
|
}
|
28
38
|
|
29
39
|
const options = this.getOptions();
|
30
|
-
return new Taxa(
|
40
|
+
return new Taxa(
|
41
|
+
getIncludeList(options.datadir),
|
42
|
+
this.getErrorLog(),
|
43
|
+
options["show-flower-errors"]
|
44
|
+
);
|
31
45
|
}
|
32
|
-
|
33
46
|
}
|
34
47
|
|
35
|
-
export { TaxaLoader };
|
48
|
+
export { TaxaLoader };
|
package/lib/taxaprocessor.js
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
import { TaxaLoader } from "./taxaloader.js";
|
2
2
|
|
3
3
|
class TaxaProcessor {
|
4
|
-
|
5
4
|
#options;
|
6
5
|
#taxaLoaderClass;
|
7
6
|
#taxaLoader;
|
8
7
|
|
9
|
-
constructor(
|
8
|
+
constructor(options, taxaLoaderClass = TaxaLoader) {
|
10
9
|
this.#options = options;
|
11
10
|
this.#taxaLoaderClass = taxaLoaderClass;
|
12
11
|
}
|
@@ -23,14 +22,13 @@ class TaxaProcessor {
|
|
23
22
|
return this.#taxaLoader.getTaxa();
|
24
23
|
}
|
25
24
|
|
26
|
-
async process(
|
27
|
-
console.log(
|
28
|
-
this.#taxaLoader = new this.#taxaLoaderClass(
|
25
|
+
async process(commandRunner) {
|
26
|
+
console.log("loading data");
|
27
|
+
this.#taxaLoader = new this.#taxaLoaderClass(this.#options);
|
29
28
|
await this.#taxaLoader.load();
|
29
|
+
await commandRunner(this);
|
30
30
|
this.#taxaLoader.writeErrorLog();
|
31
|
-
await commandRunner( this );
|
32
31
|
}
|
33
|
-
|
34
32
|
}
|
35
33
|
|
36
|
-
export { TaxaProcessor };
|
34
|
+
export { TaxaProcessor };
|