Skip to content

Commit

Permalink
Merge pull request #72 from phyloref/add-apomorphy-phyloref
Browse files Browse the repository at this point in the history
This PR adds an apomorphy-based phyloreference for testing "Testudinata". It also adds support for the "apomorphy" field in phyloreferences, makes an apomorphy-based phyloreference (i.e. one containing an apomorphy and a single internal specifier) valid, provides instructions on generating the correct JSON-LD for those and modifies the tests so that apomorphy-based phyloreferences can pass testing. In adding these features, I found three bugs, which I also fix in this PR:
- The `test/example.js` script was only testing the Brochu 2003 test file. It now tests all JSON files in the `test/examples/correct` directory.
- The nomenclatural codes in the JSON Schema file were enumerated in multiple places. This PR moves that enumeration to a single place and refers to them as needed elsewhere in the schema.
- The code for generating author names in bibliographic citations required that a `name` field be provided for each author. It can now generate a name from a combination of `lastname`, `firstname` and `middlename` fields.
  • Loading branch information
gaurav authored Mar 9, 2021
2 parents 1672322 + caa911e commit 555ab3d
Show file tree
Hide file tree
Showing 13 changed files with 1,085 additions and 693 deletions.
5 changes: 4 additions & 1 deletion docs/context/development/phyx.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"owl": "http://www.w3.org/2002/07/owl#",
"skos": "http://www.w3.org/2004/02/skos/core#",

"obo": "http://purl.obolibrary.org/obo/",
"dwc": "http://rs.tdwg.org/dwc/terms/",
Expand Down Expand Up @@ -188,6 +189,8 @@
"source": "dct:source",
"bibliographicCitation": "dct:bibliographicCitation",

"representsTaxonomicUnits": "obo:CDAO_0000187"
"representsTaxonomicUnits": "obo:CDAO_0000187",

"apomorphy": "phyloref:apomorphy"
}
}
72 changes: 54 additions & 18 deletions docs/context/development/schema.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{
"definitions": {
"nomenclaturalCodes": {
"type": "string",
"enum": [
"http://rs.tdwg.org/ontology/voc/TaxonName#ICZN",
"http://rs.tdwg.org/ontology/voc/TaxonName#ICBN",
"http://ontology.phyloref.org/tcan.owl#ICNP",
"http://ontology.phyloref.org/tcan.owl#ICTV",
"http://rs.tdwg.org/ontology/voc/TaxonName#ICNCP"
]
},
"agent": {
"type": "object",
"description": "A person or entity.",
Expand Down Expand Up @@ -115,9 +125,7 @@
}
},
"required": [
"name",
"volume",
"identifier"
"name"
]
},
"identifier": {
Expand Down Expand Up @@ -184,15 +192,8 @@
]
},
"nomenclaturalCode": {
"type": "string",
"description": "The nomenclatural code under which this name is defined.",
"enum": [
"http://purl.obolibrary.org/obo/NOMEN_0000036",
"http://purl.obolibrary.org/obo/NOMEN_0000107",
"http://purl.obolibrary.org/obo/NOMEN_0000109",
"http://purl.obolibrary.org/obo/NOMEN_0000110",
"http://purl.obolibrary.org/obo/NOMEN_0000111"
]
"$ref": "#/definitions/nomenclaturalCodes"
},
"label": {
"type": "string",
Expand Down Expand Up @@ -227,15 +228,21 @@
"description": "Every Phyx file should have a @context, making it a valid JSON-LD file.",
"minLength": 1
},
"citation": {
"description": "A citation to the source of this Phyx file.",
"$ref": "#/definitions/citation"
},
"defaultNomenclaturalCodeIRI": {
"description": "The default nomenclatural code to use for new phyloreferences in this Phyx file.",
"enum": [
"http://purl.obolibrary.org/obo/NOMEN_0000036",
"http://purl.obolibrary.org/obo/NOMEN_0000107",
"http://purl.obolibrary.org/obo/NOMEN_0000109",
"http://purl.obolibrary.org/obo/NOMEN_0000110",
"http://purl.obolibrary.org/obo/NOMEN_0000111"
]
"$ref": "#/definitions/nomenclaturalCodes"
},
"@type": {
"type": "array",
"description": "Addition RDF types for the top-level object. `owl:Ontology` is added automatically."
},
"owl:imports": {
"type": "array",
"description": "A list of additional OWL imports to be added in this ontology."
},
"phylogenies": {
"type": "array",
Expand Down Expand Up @@ -332,6 +339,35 @@
"items": {
"$ref": "#/definitions/taxonomic_unit"
}
},
"apomorphy": {
"type": "object",
"description": "For a phyloreference using an apomorphy, this field describes the phenotype (or trait) being used as the apomorphy",
"additionalProperties": false,
"properties": {
"@type": {
"description": "The type of this apomorphy.",
"type": "string",
"format": "uri"
},
"definition": {
"description": "A natural language description of this apomorphy."
},
"phenotypicQuality": {
"description": "The quality of this phenotype. Must be a subclass of pato:quality.",
"type": "string",
"format": "uri"
},
"bearingEntity": {
"description": "The bearer of the phenotypic quality.",
"type": "string",
"format": "uri"
}
},
"required": [
"@type",
"definition"
]
}
}
}
Expand Down
27 changes: 24 additions & 3 deletions src/wrappers/CitationWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,44 @@ class CitationWrapper {
this.citation = citation;
}

/**
* Helper method to return a single name for a given agent entry.
* The algorithm we use is:
* - `name`, if one is present.
* - Some combination of `lastname`, `firstname` and `middlename`, if present.
*/
static getAgentName(agent) {
if (has(agent, 'name')) return agent.name;
if (has(agent, 'lastname')) {
if (has(agent, 'firstname')) {
if (has(agent, 'middlename')) {
return `${agent.firstname} ${agent.middlename} ${agent.lastname}`;
}

return `${agent.firstname} ${agent.lastname}`;
}
return `${agent.lastname}`;
}
return '(Unable to read name)';
}

/** Returns a single string with the entire bibliographic citation. */
toString() {
if (!this.citation || isEmpty(this.citation)) return undefined;

// If we already have a bibliographic citation, we can just return that.
if (has(this.citation, 'bibliographicCitation')) return this.citation.bibliographicCitation;

let authors = (this.citation.authors || []).map(author => author.name);
let authors = (this.citation.authors || []).map(CitationWrapper.getAgentName);
if (authors.length === 0) authors = ['Anonymous'];
if (authors.length > 2) authors = [`${authors[0]} et al`];
let authorsAndTitle = `${authors.join(' and ')} (${this.citation.year || 'n.d.'}) ${this.citation.title || 'Untitled'}`;

const editorLists = [];
const editors = (this.citation.editors || []).map(editor => editor.name);
const editors = (this.citation.editors || []).map(CitationWrapper.getAgentName);
if (editors.length > 0) editorLists.push(`eds: ${editors.join(' and ')}`);

const seriesEditors = (this.citation.series_editors || []).map(editor => editor.name);
const seriesEditors = (this.citation.series_editors || []).map(CitationWrapper.getAgentName);
if (seriesEditors.length > 0) editorLists.push(`series eds: ${seriesEditors.join(' and ')}`);

if (editorLists.length > 0) authorsAndTitle += ` [${editorLists.join(', ')}]`;
Expand Down
22 changes: 22 additions & 0 deletions src/wrappers/PhylorefWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,27 @@ class PhylorefWrapper {
const internalSpecifiers = phylorefAsJSONLD.internalSpecifiers || [];
const externalSpecifiers = phylorefAsJSONLD.externalSpecifiers || [];

// If it is an apomorphy-based class expression, we should generate a
// logical expression that describes the apomorphy.
const phylorefType = phylorefAsJSONLD.phylorefType;
if (
(phylorefType && phylorefType === 'phyloref:PhyloreferenceUsingApomorphy')
|| (has(phylorefAsJSONLD, 'apomorphy'))
) {
// This is an apomorphy-based definition!
phylorefAsJSONLD.subClassOf = [
'phyloref:Phyloreference',
'phyloref:PhyloreferenceUsingApomorphy',
];

// Someday, we will probably want to turn this apomorphy into a
// logical expression so that it can be computed alongside other
// OWL ontologies. This is outside our scope for the moment, so
// we will simply pass on the phyloreference as-is.

return phylorefAsJSONLD;
}

// We might need to make component classes.
// So we reset our component class counts and records.
PhylorefWrapper.componentClassCount = 0;
Expand All @@ -743,6 +764,7 @@ class PhylorefWrapper {

if (internalSpecifiers.length === 0) {
// We can't handle phyloreferences without at least one internal specifier.
calculatedPhylorefType = 'phyloref:MalformedPhyloreference';
phylorefAsJSONLD.malformedPhyloreference = 'No internal specifiers provided';
} else if (externalSpecifiers.length > 0) {
calculatedPhylorefType = 'phyloref:PhyloreferenceUsingMaximumClade';
Expand Down
19 changes: 13 additions & 6 deletions src/wrappers/PhyxWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,19 @@ class PhyxWrapper {

// Finally, add the base URI as an ontology.
if (baseIRI) jsonld['@id'] = baseIRI;
jsonld['@type'] = [owlterms.PHYLOREFERENCE_TEST_CASE, 'owl:Ontology'];
jsonld['owl:imports'] = [
'http://raw.githubusercontent.com/phyloref/curation-workflow/develop/ontologies/phyloref_testcase.owl',
'http://ontology.phyloref.org/2018-12-14/phyloref.owl',
'http://ontology.phyloref.org/2018-12-14/tcan.owl',
];

// Set up the top-level object '@type'. If one is present, we add our terms to that.
if (!has(jsonld, '@type')) jsonld['@type'] = [];
if (!Array.isArray(jsonld['@type'])) jsonld['@type'] = [jsonld['@type']];
jsonld['@type'].push(owlterms.PHYLOREFERENCE_TEST_CASE);
jsonld['@type'].push('owl:Ontology');

// Set up the ontology imports. If one is present, we add our imports to that.
if (!has(jsonld, 'owl:imports')) jsonld['owl:imports'] = [];
if (!Array.isArray(jsonld['owl:imports'])) jsonld['owl:imports'] = [jsonld['owl:imports']];
jsonld['owl:imports'].push('http://raw.githubusercontent.com/phyloref/curation-workflow/develop/ontologies/phyloref_testcase.owl');
jsonld['owl:imports'].push('http://ontology.phyloref.org/2018-12-14/phyloref.owl');
jsonld['owl:imports'].push('http://ontology.phyloref.org/2018-12-14/tcan.owl');

// If the '@context' is missing, add it here.
if (!has(jsonld, '@context')) {
Expand Down
Loading

0 comments on commit 555ab3d

Please sign in to comment.