Skip to content

Commit

Permalink
Merge pull request #138 from phyloref/add-normalize
Browse files Browse the repository at this point in the history
Add the ability to normalize wrapped objects and some other minor fixes.
  • Loading branch information
gaurav committed Jun 18, 2024
2 parents 39d383f + 2f7ac95 commit abed61c
Show file tree
Hide file tree
Showing 14 changed files with 816 additions and 203 deletions.
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.12.2
428 changes: 232 additions & 196 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions src/wrappers/CitationWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ class CitationWrapper {
this.citation = citation;
}

/**
* Return a normalized form of a citation.
*
* I'm not really sure how to normalize a citation, but the main thing we can do is delete any key
* that is equivalent to ''. We could interconvert between `name` and
* `firstname/lastname/middlename`, but that's not really equivalent, is it?
*/
static normalize(citation) {
const normalizedCitation = {};
Object.keys(citation).forEach((key) => {
// As long as citation[key] has a reasonable value, we copy it into the normalized citation.
if (citation[key]) {
normalizedCitation[key] = citation[key];
}
});
return normalizedCitation;
}

/**
* Helper method to return a single name for a given agent entry.
* The algorithm we use is:
Expand Down
21 changes: 20 additions & 1 deletion src/wrappers/PhylogenyWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
* PhylogenyWrapper
*/

const { has } = require('lodash');
const {
has,
cloneDeep,
} = require('lodash');

/** Used to parse Newick strings. */
const newickJs = require('newick-js');
Expand Down Expand Up @@ -34,6 +37,22 @@ class PhylogenyWrapper {
this.defaultNomenCode = defaultNomenCode;
}

/**
* Return a normalized form of the phylogeny.
*/
static normalize(phylogeny) {
const normalizedPhylogeny = cloneDeep(phylogeny);

// We could normalize the Newick string, but that doesn't seem very nice.

// Normalize the source if there is one.
if ('source' in phylogeny) {
normalizedPhylogeny.source = CitationWrapper.normalize(phylogeny.source || {});
}

return normalizedPhylogeny;
}

static getErrorsInNewickString(newick) {
// Given a Newick string, return a list of errors found in parsing this
// string. The errors are returned as a list of objects, each of which
Expand Down
16 changes: 16 additions & 0 deletions src/wrappers/PhylorefWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ class PhylorefWrapper {
return this.phyloref.internalSpecifiers;
}

/**
* Normalize a phyloreference.
*
* @param phyloref
*/
static normalize(phyloref) {
const normalizedPhyloref = cloneDeep(phyloref);

normalizedPhyloref.internalSpecifiers = (phyloref.internalSpecifiers || [])
.map(TaxonomicUnitWrapper.normalize);
normalizedPhyloref.externalSpecifiers = (phyloref.externalSpecifiers || [])
.map(TaxonomicUnitWrapper.normalize);

return normalizedPhyloref;
}

/** Return the external specifiers of this phyloref (if any). */
get externalSpecifiers() {
if (!has(this.phyloref, 'externalSpecifiers')) {
Expand Down
26 changes: 26 additions & 0 deletions src/wrappers/PhyxWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ class PhyxWrapper {
return owlterms.UNKNOWN_CODE;
}

/**
* Return a provided Phyx document as a normalized JSON document. We ignore most keys -- including
* keys we don't know -- but any key that can be wrapped by one of the other Wrappers in this
* package will be wrapped and normalized before being returned.
*
* Normalization is mostly needed for TaxonomicUnitWrappers and its subclasses
* (TaxonConceptWrapper, TaxonNameWrapper), since these can be represented in several essentially
* identical ways. But if we implement it at every level, we can implement comparison code in
* Klados easily.
*
* Two Phyx documents should -- upon being normalized -- be comparable with each other with
* lodash.deepEqual().
*/
static normalize(phyxDocument) {
const normalizedDocument = cloneDeep(phyxDocument);

normalizedDocument.phylorefs = (phyxDocument.phylorefs || []).map(PhylorefWrapper.normalize);
normalizedDocument.phylogenies = (phyxDocument.phylogenies || [])
.map(PhylogenyWrapper.normalize);
if ('source' in phyxDocument) {
normalizedDocument.source = CitationWrapper.normalize(phyxDocument.source);
}

return normalizedDocument;
}

/**
* Generate an executable ontology from this Phyx document. The document is mostly in JSON-LD
* already, except for three important things:
Expand Down
19 changes: 19 additions & 0 deletions src/wrappers/SpecimenWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ class SpecimenWrapper {
this.specimen = specimen;
}

/**
* Normalize the specified specimen.
* @param specimen A specimen to be normalized.
*/
static normalize(specimen) {
const wrapped = new SpecimenWrapper(specimen);
const normalizedSpecimen = {
'@type': SpecimenWrapper.TYPE_SPECIMEN,
label: wrapped.label,
'dwc:basisOfRecord': wrapped.basisOfRecord,
occurrenceID: wrapped.occurrenceID,
catalogNumber: wrapped.catalogNumber,
institutionCode: wrapped.institutionCode,
collectionCode: wrapped.collectionCode,
};
if ('@id' in specimen) normalizedSpecimen['@id'] = specimen['@id'];
return normalizedSpecimen;
}

/**
* Parse the provided occurrence ID. The two expected formats are:
* - 'urn:catalog:[institutionCode]:[collectionCode]:[catalogNumber]'
Expand Down
25 changes: 21 additions & 4 deletions src/wrappers/TaxonConceptWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ class TaxonConceptWrapper {
this.defaultNomenCode = defaultNomenCode;
}

/**
* Normalize the specified taxon concept.
* @param tc A taxon concept to be normalized.
*/
static normalize(tc) {
const wrapped = new TaxonConceptWrapper(tc);
const normalizedTC = {
'@type': TaxonConceptWrapper.TYPE_TAXON_CONCEPT,
label: wrapped.label,
hasName: TaxonNameWrapper.normalize(wrapped.taxonName),
nameString: wrapped.taxonName.nameComplete,
accordingTo: wrapped.accordingTo,
};
if ('@id' in tc) normalizedTC['@id'] = tc['@id'];
return normalizedTC;
}

/**
* Return the taxon name of this taxon concept (if any) as an object.
*/
Expand Down Expand Up @@ -89,10 +106,10 @@ class TaxonConceptWrapper {
*/
get accordingTo() {
// Do we have any accordingTo information?
if (has(this.tunit, 'accordingTo')) return this.type.accordingTo;
if (has(this.tunit, 'accordingTo')) return this.tunit.accordingTo;

// Do we have an accordingToString?
if (has(this.tunit, 'accordingToString')) return this.type.accordingToString;
if (has(this.tunit, 'accordingToString')) return this.tunit.accordingToString;

// If not, we have no accodingTo information!
return undefined;
Expand All @@ -106,10 +123,10 @@ class TaxonConceptWrapper {
*/
get accordingToString() {
// Do we have any accordingTo information?
if (has(this.tunit, 'accordingTo')) return JSON.stringify(this.type.accordingTo);
if (has(this.tunit, 'accordingTo')) return JSON.stringify(this.tunit.accordingTo);

// Do we have an accordingToString?
if (has(this.tunit, 'accordingToString')) return this.type.accordingToString;
if (has(this.tunit, 'accordingToString')) return this.tunit.accordingToString;

// If not, we have no accodingTo information!
return undefined;
Expand Down
19 changes: 19 additions & 0 deletions src/wrappers/TaxonNameWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,25 @@ class TaxonNameWrapper {
return undefined;
}

/**
* Normalize the specified taxon name.
* @param txname A taxon name to be normalized.
*/
static normalize(txname) {
const wrapped = new TaxonNameWrapper(txname);
const normalizedTxname = {
'@type': TaxonNameWrapper.TYPE_TAXON_NAME,
nomenclaturalCode: wrapped.nomenclaturalCode,
label: wrapped.label,
nameComplete: wrapped.nameComplete,
genusPart: wrapped.genusPart,
specificEpithet: wrapped.specificEpithet,
infraspecificEpithet: wrapped.infraspecificEpithet,
};
if ('@id' in txname) normalizedTxname['@id'] = txname['@id'];
return normalizedTxname;
}

/**
* Returns the nomenclatural code of this taxon name.
*/
Expand Down
19 changes: 19 additions & 0 deletions src/wrappers/TaxonomicUnitWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@ class TaxonomicUnitWrapper {
this.defaultNomenCode = defaultNomenCode;
}

/**
* Normalize the specified taxonomic unit.
* @param tunit A taxonomic unit to be normalized.
*/
static normalize(tunit) {
const wrapped = new TaxonomicUnitWrapper(tunit);
if (wrapped.taxonConcept) {
return TaxonConceptWrapper.normalize(tunit);
}
if (wrapped.specimen) {
return SpecimenWrapper.normalize(tunit);
}
if (wrapped.externalReferences) {
// External references should only have an `@id`.
return tunit;
}
return tunit;
}

/**
* What type of specifier is this? This is an array that could contain multiple
* classes, but should contain one of:
Expand Down
Loading

0 comments on commit abed61c

Please sign in to comment.