diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..ecf39d173 --- /dev/null +++ b/404.html @@ -0,0 +1,987 @@ + + + + + + + + + + + + + + + + + + + CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Adding_classes_from_another_ontology/index.html b/Adding_classes_from_another_ontology/index.html new file mode 100644 index 000000000..c7b51ccf2 --- /dev/null +++ b/Adding_classes_from_another_ontology/index.html @@ -0,0 +1,1108 @@ + + + + + + + + + + + + + + + + + + + + + + + Importing classes from another ontology - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

How to add (import) classes to the Cell Ontology (CL) from another ontology

+

NOTE To add PRO terms, please follow first the instructions to add the terms into pro obo slim.

+

1. Follow steps 1 - 5 under the heading Protege-based declaration.

+

NB: Even though the instructions state that this workflow is to be avoided, the other solutions in the current documentation are out of date.

+

2. Refresh the imports

+

To refresh the imports, open Docker so it is running in the background. Then open Terminal, navigate to src/ontology directory in the cell-ontology repository and run:

+

sh run.sh make imports/merged_import.owl

+

Running the above command requires > 8GB RAM and sufficient computational power. If the refresh fails to complete due to hardware limitations, create a new issue in GitHub detailing which class(es) need to be imported and another editor can add it on your behalf.

+

Once the imports are refreshed, return to Protégé, add the logical axioms that include the newly imported class(es) and create a pull request per standard procedure.

+

Note that the import refresh process seems to be quite laborious/computationally expensive as-is, and a centralised database approach may be an improved longterm solution.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/CL-logo.jpeg b/CL-logo.jpeg new file mode 100644 index 000000000..854c2fdab Binary files /dev/null and b/CL-logo.jpeg differ diff --git a/Create_upper_level_slim/index.html b/Create_upper_level_slim/index.html new file mode 100644 index 000000000..f1b3bcb99 --- /dev/null +++ b/Create_upper_level_slim/index.html @@ -0,0 +1,1237 @@ + + + + + + + + + + + + + + + + + + + SOP for adding a new slim: - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + +

SOP for adding a new slim:

+

Intro

+

An upper slim is a set of terms for summarising annotations. CL has both a general slim and domain-specific slims allowing for the generation of general summaries or for domain specific ones. All slims have a context that covers all cells in the domain of interest. In order to fullfil the summarising use-case, a slim should have good % coverage of the domain and, if possible, avoid excluding major cell types. The following are potentially a problem for the grouping use-case:

+
    +
  • Classes in the slim with very small numbers of subclasses: Having 0-1 subclass => no capacity to summarise.
  • +
  • Classes with disproportionately large numbers of subclasses.
  • +
  • Overlapping classes - these clash with some types of summary - e.g. pie charts.
  • +
+

Potential clashing concerns: It seems reasonable to want to make sure that very important cell types are covered and are not obscured in generating summaries, but this desire can clash with the considerations above. Some judgement is needed to balance these concerns.

+

There is no perfect solution, but some solutions are better (at fulfilling the use case) than others.

+

Name and definition

+

It is important that the name of the slim accurately reflects the content it covers. Specifically, if the intention is to cover all cell types that are specific for an organ, the slim's name should be in the format of "organ_upper_slim". This ensures clarity and helps users understand the scope of the slim.

+

Furthermore, the description of the slim should follow a consistent pattern. It is recommended to use the following structure: "a subset of general classes related to specific cell types in the [organ or specific context]". This format provides a concise and informative description of the slim, helping users identify its purpose and content.

+

What files to create and edit

+

1. Preparing the subset:

+
    +
  • Create XXX_upper_slim in Protege (change "XXX" to the subset label). See Adding a new Subset.
  • +
  • Create a CSV table with the following characteristics (Find examples in src/templates):
  • +
  • 3 columns
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
IDsubsetlabel
IDAI oboInOwl:inSubset
CL:#######http://purl.obolibrary.org/obo/cl#XXX_upper_slimCL term
...http://purl.obolibrary.org/obo/cl#XXX_upper_slim...
+
    +
  • +

    Save the CSV file with the name 'XXX_upper_slim.csv' in the src/templates directory.

    +
  • +
  • +

    Modify src/ontology/cl-odk.yaml introducing new lines for the new slim (change "XXX" to the subset label):

    +

    - id: XXX_upper_slim

    +

    image

    +
  • +
+

- filename: XXX_upper_slim.owl + use_template: True + templates: + - XXX_upper_slim.csv

+
![image](https://github.com/obophenotype/cell-ontology/assets/94959119/254ad25f-7bf2-4ac2-afe2-9ad067d9c1ea)
+
+

2. Generating the Slim OWL file:

+
    +
  • Navigate to the src/ontology file in the terminal. Make sure Docker is running.
  • +
  • Run the command:
  • +
+

sh run.sh make update_repo

+

3. Modifying the Catalog:

+
    +
  • Open the src/ontology/catalog-v001.xml file.
  • +
  • Add the following line (change "XXX" to the subset label):
  • +
+

<uri name="http://purl.obolibrary.org/obo/cl/components/XXX_upper_slim.owl" uri="components/XXX_upper_slim.owl"/>` + - image

+

4. Preparing the Upper Level Slim import to CL:

+
    +
  • Open src/ontology/cl-edit.owl.
  • +
  • +

    Add the following import statement (change "XXX" to the subset label):

    +

    Import(<http://purl.obolibrary.org/obo/cl/components/XXX_upper_slim.owl>) +image

    +
  • +
+

5. Updating the slim owl file:

+
    +
  • +

    Run the command:

    +

    sh run.sh make all_subsets -B + - Alternatively, run the following command to run it anyway even if it says it is up to date (change "XXX" to the subset label):

    +

    sh run.sh make components/XXX_upper_slim.owl -B

    +
  • +
+

6. Testing Slim Coverage:

+
    +
  • Open src/ontology/cl.Makefile.
  • +
  • +

    Add the subset label to SLIM_TEMPLATES (without _upper_slim!!!).

    +

    image

    +
  • +
  • +

    Add the term that will be used to test coverage

    +

    image

    +
  • +
  • +

    Add:

    +
  • +
+

$(REPORTDIR)/XXX_upper_slim.csv: $(TEMPLATEDIR)/XXX_upper_slim.csv + $(eval TERM_ID := $(YYY)) + $(COVERAGECMD) + (substitute "XXX" to the subset label and YYY for the tested label)

+
 ![image](https://github.com/obophenotype/cell-ontology/assets/94959119/7eb18255-0ef7-4fbc-9f7f-e582372165bf)
+
+
    +
  • Using the terminal, navigate to src/ontology.
  • +
  • +

    Run the command:

    +

    sh run.sh make slim_coverage

    +
  • +
+

Understanding reports

+

The reports can be accessed at src/ontology/reports/XXX_upper_slim.csv. First, the coverage percentage is displayed, indicating the proportion of cells covered. Secondly, the number of cells covered by each term of the subset is provided. Finally, a list is presented, indicating all the terms that were expected to be covered but are not currently included.

+

Ideally, terms would have more than 1 cell covered. Furthermore, a term covering hundreds of cells might indicate that it is too general, and a more specific term (or multiple) should be evaluated, specially if it overlaps with other terms of the subset. Example: For the eye_upper_slim, 'retinal cell' is a (too) general term that overlaps other grouping terms such as 'retinal bipolar neuron', 'retina horizontal cell' or 'amacrine cell'.

+

In the case that there is overlapping of terms (term A in the subset covers term B of the subset), a coverage file will be created and it can be accessed at src/ontology/reports/overlapping_terms_XXX_upper_slim.csv.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Fixing_xsdstring_diffs/index.html b/Fixing_xsdstring_diffs/index.html new file mode 100644 index 000000000..309a0e2ed --- /dev/null +++ b/Fixing_xsdstring_diffs/index.html @@ -0,0 +1,1100 @@ + + + + + + + + + + + + + + + + + + + + + + + Fixing unintended ^^xsd:string diffs - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Fixing ^^xsd:string Diffs

+

When you make edits, sometimes there will be large amounts of unintended differences that show up that involves the removal of ^^xsd:string. If so, you can resolve them by following normalising your cl-edit.owl file.

+

SOP

+
    +
  1. +

    Update your file from Master (see 'How to resolve merge conflicts' for instructions on how to do this including how to resolve clashes while doing this).

    +
  2. +
  3. +

    in the terminal, set directory to the ontology folder in CL: cd .../GitHub/cell-ontology/src/ontology

    +
  4. +
  5. +

    Run the normaliser in terminal:

    +
  6. +
+

If you have docker installed: sh run.sh make normalise_xsd_string

+

If you do not have docker installed: make normalise_xsd_string

+

If make is not installed, on MAC:

+
sed -i '' -E "s/Annotation[(](oboInOwl[:]hasDbXref [\"][^\"]*[\"])[)]/Annotation(\1^^xsd:string)/g" cl-edit.owl
+
+

This should resolve your ^^xsd:string issue, after which, you can handle your pull request as per usual.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Keeping_ontology_terms_up_to_date/index.html b/Keeping_ontology_terms_up_to_date/index.html new file mode 100644 index 000000000..37bea123b --- /dev/null +++ b/Keeping_ontology_terms_up_to_date/index.html @@ -0,0 +1,1159 @@ + + + + + + + + + + + + + + + + + + + + + + + Keep terms up-to-date - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Keep terms up-to-date

+ +

Keeping cell ontology annotation up to date

+

Cell ontology identifiers (IRIs) are never lost, but they are occasionally deprecated. On the rare occasions that this happens, all logical links to other ontology terms (e.g. recording classification or partonomy) are removed and term is tagged with the annotation owl:deprecated True. To aid migration of annotations to the latest standard, these terms are also annotated with either a term_replaced_by or a consider tag. A term_replaced_by annotation is used to record the ID of a term it is safe to auto-migrate annotations to. More rarely, consider is used to record multiple potential replacement terms requiring human consideration to map. In these cases, a comment will be present to provide guidance for mapping.

+

The Ontology Lookup Service API provides a convenient way to check for deprecated terms & find replacements.

+

The term http://purl.obolibrary.org/obo/CL_0000375 has been deprecated and has that tag term_replaced_by

+

Querying the OLS API for this:

+

https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375. (Note the query IRI must be double encoded)

+

Returns:

+
{
+  "iri" : "http://purl.obolibrary.org/obo/CL_0000375",
+  "label" : "obsolete osteoprogenitor cell",
+  "description" : null,
+  "annotation" : {
+    "database_cross_reference" : [ "BTO:0002051" ],
+    "has_obo_namespace" : [ "cell" ],
+    "term replaced by" : [ "CL:0007010" ]
+  },
+  "synonyms" : null,
+  "ontology_name" : "cl",
+  "ontology_prefix" : "CL",
+  "ontology_iri" : "http://purl.obolibrary.org/obo/cl.owl",
+  "is_obsolete" : true,
+  "term_replaced_by" : "CL:0007010",
+  "is_defining_ontology" : true,
+  "has_children" : false,
+  "is_root" : true,
+  "short_form" : "CL_0000375",
+  "obo_id" : "CL:0000375",
+  "in_subset" : null,
+  "obo_definition_citation" : null,
+  "obo_xref" : [{"database":"BTO","id":"0002051","description":null,"url":"http://purl.obolibrary.org/obo/BTO_0002051"}],
+  "obo_synonym" : null,
+  "is_preferred_root" : false,
+  "_links" : {
+    "self" : {
+      "href" : "https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375"
+    },
+    "graph" : {
+      "href" : "https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375/graph"
+    }
+  }
+}
+
+

The term_replaced_by key points to the ID of a safe replacement term: CL:0007010. This is a CURIE for http://purl.obolibrary.org/obo/CL_0007010 *

+

consider

+

The term http://purl.obolibrary.org/obo/CL_0000144 has been deprecated and has a consider tag pointing to multiple possible replacement terms, along with a comment for guidance.

+

Querying the OLS API for this: https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144

+

Returns

+
{
+  "iri" : "http://purl.obolibrary.org/obo/CL_0000144",
+  "label" : "obsolete cell by function",
+  "description" : [ "OBSOLETE: A classification of cells by their primary end goal or behavior." ],
+  "annotation" : {
+    "comment" : [ "This term was made obsolete because there is no difference in meaning between it and 'cell', as any cell can be classified by its function or behavior. If you have used this term in annotation, please replace it with cell (CL:0000000), native cell (CL:0000003), or cell in vitro (CL:0001034) as appropriate." ],
+    "consider" : [ "CL:0001034", "CL:0000000", "CL:0000003" ],
+    "has_obo_namespace" : [ "cell" ]
+  },
+  "synonyms" : null,
+  "ontology_name" : "cl",
+  "ontology_prefix" : "CL",
+  "ontology_iri" : "http://purl.obolibrary.org/obo/cl.owl",
+  "is_obsolete" : true,
+  "term_replaced_by" : null,
+  "is_defining_ontology" : true,
+  "has_children" : false,
+  "is_root" : true,
+  "short_form" : "CL_0000144",
+  "obo_id" : "CL:0000144",
+  "in_subset" : null,
+  "obo_definition_citation" : [{"definition":"OBSOLETE: A classification of cells by their primary end goal or behavior.","oboXrefs":[{"database":"FB","id":"ma","description":null,"url":"http://flybase.org/reports/ma.html"}]}],
+  "obo_xref" : null,
+  "obo_synonym" : null,
+  "is_preferred_root" : false,
+  "_links" : {
+    "self" : {
+      "href" : "https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144"
+    },
+    "graph" : {
+      "href" : "https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144/graph"
+    }
+  }
+}
+
+

* Warning - due to legacy issues, the values of these tags are either a curie (CL:0000123) or short_form ID (CL_0000123) rather than an iri. Handling code needs to deal with both of these formats.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/annotation_properties/index.html b/annotation_properties/index.html new file mode 100644 index 000000000..f2d5484d8 --- /dev/null +++ b/annotation_properties/index.html @@ -0,0 +1,1321 @@ + + + + + + + + + + + + + + + + + + + + + + + CL annotation properties - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Annotation Properties

+

Note- this page is currently under development.

+

The Cell Ontology has the following annotation properties:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Annotation propertyDescriptionExample termExample annotationMust have?Only one use per term is allowed?
considerTo be used on obsoleted classes, to point to a term that should be considered by curators for use in place of the obsoleted term. Multiple consider terms are allowed. It can be useful to combine this with a comment to indicate when replacement would be appropriate.CL:0000610 obsolete plant cellPO:0009002NoNo
created_byAdded automatically on term creation with standard Protege settings. Ideally, this should use the "supplied user name" in the Protege User Details preference pane. This has been inconsistently applied in the past.CL:0002518tmeehanShouldYes
creation_dateAdded automatically on term creation with standard Protege settings.CL:00025182011-02-08T10:46:34ZShouldYes
database_cross_referenceCitable references that have helped generate the term and term's definition. Includes PubMed IDs (in the format PMID:XXXXXXXX).CL:0011005 GABAergic interneuronPMID:29724907ShouldNo
dc:contributorUse this to annotate a whole ontology file with the identifier of a contributor. ORCID preferred.N/Ahttps://orcid.org/0000-0001-9990-8331Nice to have, if applicableNo
dc:creatorComing SoonCL:0001201 B cell, CD19-positivehttps://orcid.org/0000-0001-9990-8331NoYes
dc:dateComing soonCL:0001065 innate lymphoid cell2017-01-30T20:20:48ZNoYes
dc:descriptionUse this to annotate a whole ontology file with a brief description of the ontology.N/AAn ontology of cell types.NoNo
dc:titleUse this to annotate an ontology, giving it a human readable title.N/ACell OntologyNoNo
dcterms:licenseUse to attach a license to the whole ontology file.N/Ahttp://creativecommons.org/licenses/by/4.0/NoNo
definitionThe textual definition for the ontology class.CL:0000946 antibody secreting cellA lymphocyte of B lineage that is devoted to secreting large amounts of immunoglobulin.MustYes
'expand expression to'Coming soonComing soonComing soonNoNo
foaf:depicted_byUse this to add a link to an image that depicts an example of an entity referred to by the termComing soonComing soonNoNo
has_alternative_idIn CL this is a legacy property. Do not use.CL:0000059 ameloblastCL:0000053NoNo
has_broad_synonymUsed for synonyms where the primary definition accurately describes the synonym, but the definition of the synonym may encompass other structures as well. In some cases where a broad synonym is given, it will be a broad synonym for more than one ontology term. You are encouraged to add a reference that uses the term in this way.CL:0000365 animal zygotezygoteNoNo
has_exact_synonymUsed for synonyms where the definition of the synonym is exactly the same as primary term definition. This is used when the same class can have more than one name. You are encouraged to add a reference that uses the term in this way.CL:0000622 acinar cellacinic cellNice to have, if applicableNo
has_narrow_synonymUsed for synonyms where the definition of the synonym is the same as the primary definition, but has additional qualifiers. You are encouraged to add a reference that uses the term in this way.CL:0000362 epidermal cellepithelial cell of skinNoNo
has_obo_namespaceThis is a legacy annotation property. Do not add this manually.CL:0001061 abnormal cellcellNoNo
has_related_synonymThis scope is applied when a word of phrase has been used synonymously with the primary term name in the literature, but the usage is not strictly correct. That is, the synonym in fact has a slightly different meaning than the primary term name. Since users may not be aware that the synonym was being used incorrectly when searching for a term, related synonyms are included.CL:0000902 induced T-regulatory celladaptive TregNoNo
has_synonym_typeThe target of this relation must be an annotation property of type 'synonym_type_property'.N/AN/ANoNo
IAO_0000116Coming soonComing soonComing soonNoNo
idAutomatically added by some pathways. Do not add manually. If duplicating a term (with the duplicate getting a new ID), it should be deleted.CL:2000074 splenocyteCL:2000074YesYes
in_subsetUsed to add subset tags, used in conjunction with subset_propertyCL:0000039 germ line cell_upper_levelNoNo
is_inferredThis annotation property is used in some automated pipelines. Do not add manuallyComing soonComing soonNoNo
rdfs:commentUse to add a clarifying comment to a term. This can be useful for adding examples and for clarifying terminological confusions.CL:0007016 adaxial cellIn teleosts, adaxial cells give rise to slow muscle myoblasts.NoYes
rdfs:isDefinedByDo not add manually.Coming soonComing soonNoYes
rdfs:labelPrimary name - used as a display name by Protege (with standard settings) and most downstream consumers. Add only one of these. It must be unique within an ontology.CL:0000418 arcade cellarcade cellMustYes
RO_0002161Coming soonComing soonNoNo
'see also'Used to link to a webpage, such as a GitHub ticket.CL:0000134 mesenchymal stem cellhttps://github.com/obophenotype/cell-ontology/issues/474NoNo
shorthandAdded automatically by some pipelines. Do not add manuallyComing soonComing soonNoNo
subset_propertyA grouping class for subset tags.N/AN/ANoNo
subset_property: added_for_HCAA subset tag for terms that were requested by the Human Cell Atlas.N/AN/ANoNo
subset property: location_groupingA subset tag for cell types from a particular anatomical location.N/AN/ANoNo
synonym_type_propertyA grouping class for synonym tags.N/AN/ANoNo
'term replaced by'To be used on obsolete terms to indicate a term that can be automatically substituted for the obsoleted term.Coming soonComing soonNoNo
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.1e8ae164.min.js b/assets/javascripts/bundle.1e8ae164.min.js new file mode 100644 index 000000000..212979889 --- /dev/null +++ b/assets/javascripts/bundle.1e8ae164.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var _i=Object.create;var br=Object.defineProperty;var Ai=Object.getOwnPropertyDescriptor;var Ci=Object.getOwnPropertyNames,Ft=Object.getOwnPropertySymbols,ki=Object.getPrototypeOf,vr=Object.prototype.hasOwnProperty,eo=Object.prototype.propertyIsEnumerable;var Zr=(e,t,r)=>t in e?br(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,F=(e,t)=>{for(var r in t||(t={}))vr.call(t,r)&&Zr(e,r,t[r]);if(Ft)for(var r of Ft(t))eo.call(t,r)&&Zr(e,r,t[r]);return e};var to=(e,t)=>{var r={};for(var o in e)vr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Ft)for(var o of Ft(e))t.indexOf(o)<0&&eo.call(e,o)&&(r[o]=e[o]);return r};var gr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Hi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Ci(t))!vr.call(e,n)&&n!==r&&br(e,n,{get:()=>t[n],enumerable:!(o=Ai(t,n))||o.enumerable});return e};var jt=(e,t,r)=>(r=e!=null?_i(ki(e)):{},Hi(t||!e||!e.__esModule?br(r,"default",{value:e,enumerable:!0}):r,e));var ro=(e,t,r)=>new Promise((o,n)=>{var i=c=>{try{s(r.next(c))}catch(p){n(p)}},a=c=>{try{s(r.throw(c))}catch(p){n(p)}},s=c=>c.done?o(c.value):Promise.resolve(c.value).then(i,a);s((r=r.apply(e,t)).next())});var no=gr((xr,oo)=>{(function(e,t){typeof xr=="object"&&typeof oo!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(C){return!!(C&&C!==document&&C.nodeName!=="HTML"&&C.nodeName!=="BODY"&&"classList"in C&&"contains"in C.classList)}function c(C){var ct=C.type,Ne=C.tagName;return!!(Ne==="INPUT"&&a[ct]&&!C.readOnly||Ne==="TEXTAREA"&&!C.readOnly||C.isContentEditable)}function p(C){C.classList.contains("focus-visible")||(C.classList.add("focus-visible"),C.setAttribute("data-focus-visible-added",""))}function l(C){C.hasAttribute("data-focus-visible-added")&&(C.classList.remove("focus-visible"),C.removeAttribute("data-focus-visible-added"))}function f(C){C.metaKey||C.altKey||C.ctrlKey||(s(r.activeElement)&&p(r.activeElement),o=!0)}function u(C){o=!1}function h(C){s(C.target)&&(o||c(C.target))&&p(C.target)}function w(C){s(C.target)&&(C.target.classList.contains("focus-visible")||C.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(C.target))}function A(C){document.visibilityState==="hidden"&&(n&&(o=!0),Z())}function Z(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function te(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(C){C.target.nodeName&&C.target.nodeName.toLowerCase()==="html"||(o=!1,te())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",A,!0),Z(),r.addEventListener("focus",h,!0),r.addEventListener("blur",w,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var zr=gr((kt,Vr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof kt=="object"&&typeof Vr=="object"?Vr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof kt=="object"?kt.ClipboardJS=r():t.ClipboardJS=r()})(kt,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Li}});var a=i(279),s=i.n(a),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(D){try{return document.execCommand(D)}catch(M){return!1}}var h=function(M){var O=f()(M);return u("cut"),O},w=h;function A(D){var M=document.documentElement.getAttribute("dir")==="rtl",O=document.createElement("textarea");O.style.fontSize="12pt",O.style.border="0",O.style.padding="0",O.style.margin="0",O.style.position="absolute",O.style[M?"right":"left"]="-9999px";var I=window.pageYOffset||document.documentElement.scrollTop;return O.style.top="".concat(I,"px"),O.setAttribute("readonly",""),O.value=D,O}var Z=function(M,O){var I=A(M);O.container.appendChild(I);var W=f()(I);return u("copy"),I.remove(),W},te=function(M){var O=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},I="";return typeof M=="string"?I=Z(M,O):M instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(M==null?void 0:M.type)?I=Z(M.value,O):(I=f()(M),u("copy")),I},J=te;function C(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?C=function(O){return typeof O}:C=function(O){return O&&typeof Symbol=="function"&&O.constructor===Symbol&&O!==Symbol.prototype?"symbol":typeof O},C(D)}var ct=function(){var M=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},O=M.action,I=O===void 0?"copy":O,W=M.container,K=M.target,Ce=M.text;if(I!=="copy"&&I!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(K!==void 0)if(K&&C(K)==="object"&&K.nodeType===1){if(I==="copy"&&K.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(I==="cut"&&(K.hasAttribute("readonly")||K.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Ce)return J(Ce,{container:W});if(K)return I==="cut"?w(K):J(K,{container:W})},Ne=ct;function Pe(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Pe=function(O){return typeof O}:Pe=function(O){return O&&typeof Symbol=="function"&&O.constructor===Symbol&&O!==Symbol.prototype?"symbol":typeof O},Pe(D)}function xi(D,M){if(!(D instanceof M))throw new TypeError("Cannot call a class as a function")}function Xr(D,M){for(var O=0;O0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof W.action=="function"?W.action:this.defaultAction,this.target=typeof W.target=="function"?W.target:this.defaultTarget,this.text=typeof W.text=="function"?W.text:this.defaultText,this.container=Pe(W.container)==="object"?W.container:document.body}},{key:"listenClick",value:function(W){var K=this;this.listener=p()(W,"click",function(Ce){return K.onClick(Ce)})}},{key:"onClick",value:function(W){var K=W.delegateTarget||W.currentTarget,Ce=this.action(K)||"copy",It=Ne({action:Ce,container:this.container,target:this.target(K),text:this.text(K)});this.emit(It?"success":"error",{action:Ce,text:It,trigger:K,clearSelection:function(){K&&K.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(W){return hr("action",W)}},{key:"defaultTarget",value:function(W){var K=hr("target",W);if(K)return document.querySelector(K)}},{key:"defaultText",value:function(W){return hr("text",W)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(W){var K=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(W,K)}},{key:"cut",value:function(W){return w(W)}},{key:"isSupported",value:function(){var W=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],K=typeof W=="string"?[W]:W,Ce=!!document.queryCommandSupported;return K.forEach(function(It){Ce=Ce&&!!document.queryCommandSupported(It)}),Ce}}]),O}(s()),Li=Mi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,h,w){var A=p.apply(this,arguments);return l.addEventListener(u,A,w),{destroy:function(){l.removeEventListener(u,A,w)}}}function c(l,f,u,h,w){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(A){return s(A,f,u,h,w)}))}function p(l,f,u,h){return function(w){w.delegateTarget=a(w.target,f),w.delegateTarget&&h.call(l,w)}}o.exports=c},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function c(u,h,w){if(!u&&!h&&!w)throw new Error("Missing required arguments");if(!a.string(h))throw new TypeError("Second argument must be a String");if(!a.fn(w))throw new TypeError("Third argument must be a Function");if(a.node(u))return p(u,h,w);if(a.nodeList(u))return l(u,h,w);if(a.string(u))return f(u,h,w);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,h,w){return u.addEventListener(h,w),{destroy:function(){u.removeEventListener(h,w)}}}function l(u,h,w){return Array.prototype.forEach.call(u,function(A){A.addEventListener(h,w)}),{destroy:function(){Array.prototype.forEach.call(u,function(A){A.removeEventListener(h,w)})}}}function f(u,h,w){return s(document.body,u,h,w)}o.exports=c},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),a=c.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function p(){c.off(i,p),a.apply(s,arguments)}return p._=a,this.on(i,p,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=s.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var Va=/["'&<>]/;qn.exports=za;function za(e){var t=""+e,r=Va.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function V(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function z(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||s(u,h)})})}function s(u,h){try{c(o[u](h))}catch(w){f(i[0][3],w)}}function c(u){u.value instanceof ot?Promise.resolve(u.value.v).then(p,l):f(i[0][2],u)}function p(u){s("next",u)}function l(u){s("throw",u)}function f(u,h){u(h),i.shift(),i.length&&s(i[0][0],i[0][1])}}function so(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof ue=="function"?ue(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,c){a=e[i](a),n(s,c,a.done,a.value)})}}function n(i,a,s,c){Promise.resolve(c).then(function(p){i({value:p,done:s})},a)}}function k(e){return typeof e=="function"}function pt(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Wt=pt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=ue(a),c=s.next();!c.done;c=s.next()){var p=c.value;p.remove(this)}}catch(A){t={error:A}}finally{try{c&&!c.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(k(l))try{l()}catch(A){i=A instanceof Wt?A.errors:[A]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=ue(f),h=u.next();!h.done;h=u.next()){var w=h.value;try{co(w)}catch(A){i=i!=null?i:[],A instanceof Wt?i=z(z([],V(i)),V(A.errors)):i.push(A)}}}catch(A){o={error:A}}finally{try{h&&!h.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Wt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)co(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Er=Ie.EMPTY;function Dt(e){return e instanceof Ie||e&&"closed"in e&&k(e.remove)&&k(e.add)&&k(e.unsubscribe)}function co(e){k(e)?e():e.unsubscribe()}var ke={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var lt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Er:(this.currentObservers=null,s.push(r),new Ie(function(){o.currentObservers=null,Ve(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new vo(r,o)},t}(j);var vo=function(e){se(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Er},t}(g);var St={now:function(){return(St.delegate||Date).now()},delegate:void 0};var Ot=function(e){se(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=St);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,c=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(ut.cancelAnimationFrame(o),r._scheduled=void 0)},t}(zt);var yo=function(e){se(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(qt);var de=new yo(xo);var L=new j(function(e){return e.complete()});function Kt(e){return e&&k(e.schedule)}function _r(e){return e[e.length-1]}function Je(e){return k(_r(e))?e.pop():void 0}function Ae(e){return Kt(_r(e))?e.pop():void 0}function Qt(e,t){return typeof _r(e)=="number"?e.pop():t}var dt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Yt(e){return k(e==null?void 0:e.then)}function Bt(e){return k(e[ft])}function Gt(e){return Symbol.asyncIterator&&k(e==null?void 0:e[Symbol.asyncIterator])}function Jt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Di(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Xt=Di();function Zt(e){return k(e==null?void 0:e[Xt])}function er(e){return ao(this,arguments,function(){var r,o,n,i;return Ut(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,ot(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,ot(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,ot(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function tr(e){return k(e==null?void 0:e.getReader)}function N(e){if(e instanceof j)return e;if(e!=null){if(Bt(e))return Ni(e);if(dt(e))return Vi(e);if(Yt(e))return zi(e);if(Gt(e))return Eo(e);if(Zt(e))return qi(e);if(tr(e))return Ki(e)}throw Jt(e)}function Ni(e){return new j(function(t){var r=e[ft]();if(k(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Vi(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):ce,ye(1),r?Qe(t):jo(function(){return new or}))}}function $r(e){return e<=0?function(){return L}:x(function(t,r){var o=[];t.subscribe(S(r,function(n){o.push(n),e=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s;return function(p){var l,f,u,h=0,w=!1,A=!1,Z=function(){f==null||f.unsubscribe(),f=void 0},te=function(){Z(),l=u=void 0,w=A=!1},J=function(){var C=l;te(),C==null||C.unsubscribe()};return x(function(C,ct){h++,!A&&!w&&Z();var Ne=u=u!=null?u:r();ct.add(function(){h--,h===0&&!A&&!w&&(f=Pr(J,c))}),Ne.subscribe(ct),!l&&h>0&&(l=new it({next:function(Pe){return Ne.next(Pe)},error:function(Pe){A=!0,Z(),f=Pr(te,n,Pe),Ne.error(Pe)},complete:function(){w=!0,Z(),f=Pr(te,a),Ne.complete()}}),N(C).subscribe(l))})(p)}}function Pr(e,t){for(var r=[],o=2;oe.next(document)),e}function R(e,t=document){return Array.from(t.querySelectorAll(e))}function P(e,t=document){let r=me(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function me(e,t=document){return t.querySelector(e)||void 0}function Re(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var la=T(d(document.body,"focusin"),d(document.body,"focusout")).pipe(be(1),q(void 0),m(()=>Re()||document.body),B(1));function vt(e){return la.pipe(m(t=>e.contains(t)),Y())}function Vo(e,t){return T(d(e,"mouseenter").pipe(m(()=>!0)),d(e,"mouseleave").pipe(m(()=>!1))).pipe(t?be(t):ce,q(!1))}function Ue(e){return{x:e.offsetLeft,y:e.offsetTop}}function zo(e){return T(d(window,"load"),d(window,"resize")).pipe(Me(0,de),m(()=>Ue(e)),q(Ue(e)))}function ir(e){return{x:e.scrollLeft,y:e.scrollTop}}function et(e){return T(d(e,"scroll"),d(window,"resize")).pipe(Me(0,de),m(()=>ir(e)),q(ir(e)))}function qo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)qo(e,r)}function E(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)qo(o,n);return o}function ar(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function gt(e){let t=E("script",{src:e});return H(()=>(document.head.appendChild(t),T(d(t,"load"),d(t,"error").pipe(v(()=>Ar(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),ye(1))))}var Ko=new g,ma=H(()=>typeof ResizeObserver=="undefined"?gt("https://unpkg.com/resize-observer-polyfill"):$(void 0)).pipe(m(()=>new ResizeObserver(e=>{for(let t of e)Ko.next(t)})),v(e=>T(qe,$(e)).pipe(_(()=>e.disconnect()))),B(1));function pe(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Ee(e){return ma.pipe(y(t=>t.observe(e)),v(t=>Ko.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(()=>pe(e)))),q(pe(e)))}function xt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function sr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var Qo=new g,fa=H(()=>$(new IntersectionObserver(e=>{for(let t of e)Qo.next(t)},{threshold:0}))).pipe(v(e=>T(qe,$(e)).pipe(_(()=>e.disconnect()))),B(1));function yt(e){return fa.pipe(y(t=>t.observe(e)),v(t=>Qo.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function Yo(e,t=16){return et(e).pipe(m(({y:r})=>{let o=pe(e),n=xt(e);return r>=n.height-o.height-t}),Y())}var cr={drawer:P("[data-md-toggle=drawer]"),search:P("[data-md-toggle=search]")};function Bo(e){return cr[e].checked}function Be(e,t){cr[e].checked!==t&&cr[e].click()}function We(e){let t=cr[e];return d(t,"change").pipe(m(()=>t.checked),q(t.checked))}function ua(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function da(){return T(d(window,"compositionstart").pipe(m(()=>!0)),d(window,"compositionend").pipe(m(()=>!1))).pipe(q(!1))}function Go(){let e=d(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:Bo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Re();if(typeof o!="undefined")return!ua(o,r)}return!0}),le());return da().pipe(v(t=>t?L:e))}function ve(){return new URL(location.href)}function st(e,t=!1){if(G("navigation.instant")&&!t){let r=E("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Jo(){return new g}function Xo(){return location.hash.slice(1)}function Zo(e){let t=E("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function ha(e){return T(d(window,"hashchange"),e).pipe(m(Xo),q(Xo()),b(t=>t.length>0),B(1))}function en(e){return ha(e).pipe(m(t=>me(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function At(e){let t=matchMedia(e);return nr(r=>t.addListener(()=>r(t.matches))).pipe(q(t.matches))}function tn(){let e=matchMedia("print");return T(d(window,"beforeprint").pipe(m(()=>!0)),d(window,"afterprint").pipe(m(()=>!1))).pipe(q(e.matches))}function Ur(e,t){return e.pipe(v(r=>r?t():L))}function Wr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function De(e,t){return Wr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),B(1))}function rn(e,t){let r=new DOMParser;return Wr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),B(1))}function on(e,t){let r=new DOMParser;return Wr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),B(1))}function nn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function an(){return T(d(window,"scroll",{passive:!0}),d(window,"resize",{passive:!0})).pipe(m(nn),q(nn()))}function sn(){return{width:innerWidth,height:innerHeight}}function cn(){return d(window,"resize",{passive:!0}).pipe(m(sn),q(sn()))}function pn(){return Q([an(),cn()]).pipe(m(([e,t])=>({offset:e,size:t})),B(1))}function pr(e,{viewport$:t,header$:r}){let o=t.pipe(X("size")),n=Q([o,r]).pipe(m(()=>Ue(e)));return Q([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:c,y:p}])=>({offset:{x:a.x-c,y:a.y-p+i},size:s})))}function ba(e){return d(e,"message",t=>t.data)}function va(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function ln(e,t=new Worker(e)){let r=ba(t),o=va(t),n=new g;n.subscribe(o);let i=o.pipe(ee(),oe(!0));return n.pipe(ee(),$e(r.pipe(U(i))),le())}var ga=P("#__config"),Et=JSON.parse(ga.textContent);Et.base=`${new URL(Et.base,ve())}`;function we(){return Et}function G(e){return Et.features.includes(e)}function ge(e,t){return typeof t!="undefined"?Et.translations[e].replace("#",t.toString()):Et.translations[e]}function Te(e,t=document){return P(`[data-md-component=${e}]`,t)}function ie(e,t=document){return R(`[data-md-component=${e}]`,t)}function xa(e){let t=P(".md-typeset > :first-child",e);return d(t,"click",{once:!0}).pipe(m(()=>P(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function mn(e){if(!G("announce.dismiss")||!e.childElementCount)return L;if(!e.hidden){let t=P(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),xa(e).pipe(y(r=>t.next(r)),_(()=>t.complete()),m(r=>F({ref:e},r)))})}function ya(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function fn(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),ya(e,t).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))}function Ct(e,t){return t==="inline"?E("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},E("div",{class:"md-tooltip__inner md-typeset"})):E("div",{class:"md-tooltip",id:e,role:"tooltip"},E("div",{class:"md-tooltip__inner md-typeset"}))}function un(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return E("aside",{class:"md-annotation",tabIndex:0},Ct(t),E("a",{href:r,class:"md-annotation__index",tabIndex:-1},E("span",{"data-md-annotation-id":e})))}else return E("aside",{class:"md-annotation",tabIndex:0},Ct(t),E("span",{class:"md-annotation__index",tabIndex:-1},E("span",{"data-md-annotation-id":e})))}function dn(e){return E("button",{class:"md-clipboard md-icon",title:ge("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function Dr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,E("del",null,p)," "],[]).slice(0,-1),i=we(),a=new URL(e.location,i.base);G("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:s}=we();return E("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},E("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&E("div",{class:"md-search-result__icon md-icon"}),r>0&&E("h1",null,e.title),r<=0&&E("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&e.tags.map(c=>{let p=s?c in s?`md-tag-icon md-tag--${s[c]}`:"md-tag-icon":"";return E("span",{class:`md-tag ${p}`},c)}),o>0&&n.length>0&&E("p",{class:"md-search-result__terms"},ge("search.result.term.missing"),": ",...n)))}function hn(e){let t=e[0].score,r=[...e],o=we(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreDr(l,1)),...c.length?[E("details",{class:"md-search-result__more"},E("summary",{tabIndex:-1},E("div",null,c.length>0&&c.length===1?ge("search.result.more.one"):ge("search.result.more.other",c.length))),...c.map(l=>Dr(l,1)))]:[]];return E("li",{class:"md-search-result__item"},p)}function bn(e){return E("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>E("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?ar(r):r)))}function Nr(e){let t=`tabbed-control tabbed-control--${e}`;return E("div",{class:t,hidden:!0},E("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function vn(e){return E("div",{class:"md-typeset__scrollwrap"},E("div",{class:"md-typeset__table"},e))}function Ea(e){let t=we(),r=new URL(`../${e.version}/`,t.base);return E("li",{class:"md-version__item"},E("a",{href:`${r}`,class:"md-version__link"},e.title))}function gn(e,t){return e=e.filter(r=>{var o;return!((o=r.properties)!=null&&o.hidden)}),E("div",{class:"md-version"},E("button",{class:"md-version__current","aria-label":ge("select.version")},t.title),E("ul",{class:"md-version__list"},e.map(Ea)))}var wa=0;function Ta(e,t){document.body.append(e);let{width:r}=pe(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=sr(t),n=typeof o!="undefined"?et(o):$({x:0,y:0}),i=T(vt(t),Vo(t)).pipe(Y());return Q([i,n]).pipe(m(([a,s])=>{let{x:c,y:p}=Ue(t),l=pe(t),f=t.closest("table");return f&&t.parentElement&&(c+=f.offsetLeft+t.parentElement.offsetLeft,p+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:c-s.x+l.width/2-r/2,y:p-s.y+l.height+8}}}))}function Ge(e){let t=e.title;if(!t.length)return L;let r=`__tooltip_${wa++}`,o=Ct(r,"inline"),n=P(".md-typeset",o);return n.innerHTML=t,H(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),T(i.pipe(b(({active:a})=>a)),i.pipe(be(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,de)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(_t(125,de),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ta(o,e).pipe(y(a=>i.next(a)),_(()=>i.complete()),m(a=>F({ref:e},a)))}).pipe(ze(ae))}function Sa(e,t){let r=H(()=>Q([zo(e),et(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=pe(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return vt(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),ye(+!o||1/0))))}function xn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new g,a=i.pipe(ee(),oe(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),yt(e).pipe(U(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),T(i.pipe(b(({active:s})=>s)),i.pipe(be(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,de)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(_t(125,de),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),d(n,"click").pipe(U(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),d(n,"mousedown").pipe(U(a),ne(i)).subscribe(([s,{active:c}])=>{var p;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(c){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Re())==null||p.blur()}}),r.pipe(U(a),b(s=>s===o),Ye(125)).subscribe(()=>e.focus()),Sa(e,t).pipe(y(s=>i.next(s)),_(()=>i.complete()),m(s=>F({ref:e},s)))})}function Oa(e){return e.tagName==="CODE"?R(".c, .c1, .cm",e):[e]}function Ma(e){let t=[];for(let r of Oa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,c]=a;if(typeof c=="undefined"){let p=i.splitText(a.index);i=p.splitText(s.length),t.push(p)}else{i.textContent=s,t.push(i);break}}}}return t}function yn(e,t){t.append(...Array.from(e.childNodes))}function lr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Ma(t)){let[,c]=s.textContent.match(/\((\d+)\)/);me(`:scope > li:nth-child(${c})`,e)&&(a.set(c,un(c,i)),s.replaceWith(a.get(c)))}return a.size===0?L:H(()=>{let s=new g,c=s.pipe(ee(),oe(!0)),p=[];for(let[l,f]of a)p.push([P(".md-typeset",f),P(`:scope > li:nth-child(${l})`,e)]);return o.pipe(U(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?yn(f,u):yn(u,f)}),T(...[...a].map(([,l])=>xn(l,t,{target$:r}))).pipe(_(()=>s.complete()),le())})}function En(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return En(t)}}function wn(e,t){return H(()=>{let r=En(e);return typeof r!="undefined"?lr(r,e,t):L})}var Tn=jt(zr());var La=0;function Sn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Sn(t)}}function _a(e){return Ee(e).pipe(m(({width:t})=>({scrollable:xt(e).width>t})),X("scrollable"))}function On(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new g,i=n.pipe($r(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Tn.default.isSupported()&&(e.closest(".copy")||G("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${La++}`;let p=dn(c.id);c.insertBefore(p,e),G("content.tooltips")&&a.push(Ge(p))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=Sn(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||G("content.code.annotate"))){let p=lr(c,e,t);a.push(Ee(s).pipe(U(i),m(({width:l,height:f})=>l&&f),Y(),v(l=>l?p:L)))}}return _a(e).pipe(y(c=>n.next(c)),_(()=>n.complete()),m(c=>F({ref:e},c)),$e(...a))});return G("content.lazy")?yt(e).pipe(b(n=>n),ye(1),v(()=>o)):o}function Aa(e,{target$:t,print$:r}){let o=!0;return T(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),y(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Mn(e,t){return H(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),Aa(e,t).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))})}var Ln=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var qr,ka=0;function Ha(){return typeof mermaid=="undefined"||mermaid instanceof Element?gt("https://unpkg.com/mermaid@10.7.0/dist/mermaid.min.js"):$(void 0)}function _n(e){return e.classList.remove("mermaid"),qr||(qr=Ha().pipe(y(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Ln,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),B(1))),qr.subscribe(()=>ro(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${ka++}`,r=E("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),qr.pipe(m(()=>({ref:e})))}var An=E("table");function Cn(e){return e.replaceWith(An),An.replaceWith(vn(e)),$({ref:e})}function $a(e){let t=e.find(r=>r.checked)||e[0];return T(...e.map(r=>d(r,"change").pipe(m(()=>P(`label[for="${r.id}"]`))))).pipe(q(P(`label[for="${t.id}"]`)),m(r=>({active:r})))}function kn(e,{viewport$:t,target$:r}){let o=P(".tabbed-labels",e),n=R(":scope > input",e),i=Nr("prev");e.append(i);let a=Nr("next");return e.append(a),H(()=>{let s=new g,c=s.pipe(ee(),oe(!0));Q([s,Ee(e)]).pipe(U(c),Me(1,de)).subscribe({next([{active:p},l]){let f=Ue(p),{width:u}=pe(p);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let h=ir(o);(f.xh.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),Q([et(o),Ee(o)]).pipe(U(c)).subscribe(([p,l])=>{let f=xt(o);i.hidden=p.x<16,a.hidden=p.x>f.width-l.width-16}),T(d(i,"click").pipe(m(()=>-1)),d(a,"click").pipe(m(()=>1))).pipe(U(c)).subscribe(p=>{let{width:l}=pe(o);o.scrollBy({left:l*p,behavior:"smooth"})}),r.pipe(U(c),b(p=>n.includes(p))).subscribe(p=>p.click()),o.classList.add("tabbed-labels--linked");for(let p of n){let l=P(`label[for="${p.id}"]`);l.replaceChildren(E("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),d(l.firstElementChild,"click").pipe(U(c),b(f=>!(f.metaKey||f.ctrlKey)),y(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return G("content.tabs.link")&&s.pipe(Le(1),ne(t)).subscribe(([{active:p},{offset:l}])=>{let f=p.innerText.trim();if(p.hasAttribute("data-md-switching"))p.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let w of R("[data-tabs]"))for(let A of R(":scope > input",w)){let Z=P(`label[for="${A.id}"]`);if(Z!==p&&Z.innerText.trim()===f){Z.setAttribute("data-md-switching",""),A.click();break}}window.scrollTo({top:e.offsetTop-u});let h=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...h])])}}),s.pipe(U(c)).subscribe(()=>{for(let p of R("audio, video",e))p.pause()}),$a(n).pipe(y(p=>s.next(p)),_(()=>s.complete()),m(p=>F({ref:e},p)))}).pipe(ze(ae))}function Hn(e,{viewport$:t,target$:r,print$:o}){return T(...R(".annotate:not(.highlight)",e).map(n=>wn(n,{target$:r,print$:o})),...R("pre:not(.mermaid) > code",e).map(n=>On(n,{target$:r,print$:o})),...R("pre.mermaid",e).map(n=>_n(n)),...R("table:not([class])",e).map(n=>Cn(n)),...R("details",e).map(n=>Mn(n,{target$:r,print$:o})),...R("[data-tabs]",e).map(n=>kn(n,{viewport$:t,target$:r})),...R("[title]",e).filter(()=>G("content.tooltips")).map(n=>Ge(n)))}function Ra(e,{alert$:t}){return t.pipe(v(r=>T($(!0),$(!1).pipe(Ye(2e3))).pipe(m(o=>({message:r,active:o})))))}function $n(e,t){let r=P(".md-typeset",e);return H(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ra(e,t).pipe(y(n=>o.next(n)),_(()=>o.complete()),m(n=>F({ref:e},n)))})}function Pa({viewport$:e}){if(!G("header.autohide"))return $(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Ke(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),Y()),o=We("search");return Q([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),Y(),v(n=>n?r:$(!1)),q(!1))}function Rn(e,t){return H(()=>Q([Ee(e),Pa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),Y((r,o)=>r.height===o.height&&r.hidden===o.hidden),B(1))}function Pn(e,{header$:t,main$:r}){return H(()=>{let o=new g,n=o.pipe(ee(),oe(!0));o.pipe(X("active"),je(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=fe(R("[title]",e)).pipe(b(()=>G("content.tooltips")),re(a=>Ge(a)));return r.subscribe(o),t.pipe(U(n),m(a=>F({ref:e},a)),$e(i.pipe(U(n))))})}function Ia(e,{viewport$:t,header$:r}){return pr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=pe(e);return{active:o>=n}}),X("active"))}function In(e,t){return H(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=me(".md-content h1");return typeof o=="undefined"?L:Ia(o,t).pipe(y(n=>r.next(n)),_(()=>r.complete()),m(n=>F({ref:e},n)))})}function Fn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),Y()),n=o.pipe(v(()=>Ee(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),X("bottom"))));return Q([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,a-c,i)-Math.max(0,p+c-s)),{offset:a-i,height:p,active:a-i<=c})),Y((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function Fa(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return $(...e).pipe(re(o=>d(o,"change").pipe(m(()=>o))),q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),B(1))}function jn(e){let t=R("input",e),r=E("meta",{name:"theme-color"});document.head.appendChild(r);let o=E("meta",{name:"color-scheme"});document.head.appendChild(o);let n=At("(prefers-color-scheme: light)");return H(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=c.getAttribute("data-md-color-scheme"),a.color.primary=c.getAttribute("data-md-color-primary"),a.color.accent=c.getAttribute("data-md-color-accent")}for(let[s,c]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,c);for(let s=0;sa.key==="Enter"),ne(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Te("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(Oe(ae)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),Fa(t).pipe(U(n.pipe(Le(1))),at(),y(a=>i.next(a)),_(()=>i.complete()),m(a=>F({ref:e},a)))})}function Un(e,{progress$:t}){return H(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(y(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Kr=jt(zr());function ja(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Wn({alert$:e}){Kr.default.isSupported()&&new j(t=>{new Kr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ja(P(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(y(t=>{t.trigger.focus()}),m(()=>ge("clipboard.copied"))).subscribe(e)}function Dn(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function Ua(e,t){let r=new Map;for(let o of R("url",e)){let n=P("loc",o),i=[Dn(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of R("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(Dn(new URL(s),t))}}return r}function mr(e){return on(new URL("sitemap.xml",e)).pipe(m(t=>Ua(t,new URL(e))),he(()=>$(new Map)))}function Wa(e,t){if(!(e.target instanceof Element))return L;let r=e.target.closest("a");if(r===null)return L;if(r.target||e.metaKey||e.ctrlKey)return L;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),$(new URL(r.href))):L}function Nn(e){let t=new Map;for(let r of R(":scope > *",e.head))t.set(r.outerHTML,r);return t}function Vn(e){for(let t of R("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return $(e)}function Da(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...G("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=me(o),i=me(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=Nn(document);for(let[o,n]of Nn(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Te("container");return Fe(R("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),L}),ee(),oe(document))}function zn({location$:e,viewport$:t,progress$:r}){let o=we();if(location.protocol==="file:")return L;let n=mr(o.base);$(document).subscribe(Vn);let i=d(document.body,"click").pipe(je(n),v(([c,p])=>Wa(c,p)),le()),a=d(window,"popstate").pipe(m(ve),le());i.pipe(ne(t)).subscribe(([c,{offset:p}])=>{history.replaceState(p,""),history.pushState(null,"",c)}),T(i,a).subscribe(e);let s=e.pipe(X("pathname"),v(c=>rn(c,{progress$:r}).pipe(he(()=>(st(c,!0),L)))),v(Vn),v(Da),le());return T(s.pipe(ne(e,(c,p)=>p)),e.pipe(X("pathname"),v(()=>e),X("hash")),e.pipe(Y((c,p)=>c.pathname===p.pathname&&c.hash===p.hash),v(()=>i),y(()=>history.back()))).subscribe(c=>{var p,l;history.state!==null||!c.hash?window.scrollTo(0,(l=(p=history.state)==null?void 0:p.y)!=null?l:0):(history.scrollRestoration="auto",Zo(c.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),d(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(X("offset"),be(100)).subscribe(({offset:c})=>{history.replaceState(c,"")}),s}var Qn=jt(Kn());function Yn(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,Qn.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function Ht(e){return e.type===1}function fr(e){return e.type===3}function Bn(e,t){let r=ln(e);return T($(location.protocol!=="file:"),We("search")).pipe(He(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:G("search.suggest")}}})),r}function Gn({document$:e}){let t=we(),r=De(new URL("../versions.json",t.base)).pipe(he(()=>L)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>d(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),ne(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let c=s.href;return!i.target.closest(".md-version")&&n.get(c)===a?L:(i.preventDefault(),$(c))}}return L}),v(i=>{let{version:a}=n.get(i);return mr(new URL(i)).pipe(m(s=>{let p=ve().href.replace(t.base,"");return s.has(p.split("#")[0])?new URL(`../${a}/${p}`,t.base):new URL(i)}))})))).subscribe(n=>st(n,!0)),Q([r,o]).subscribe(([n,i])=>{P(".md-header__topic").appendChild(gn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let c of s)for(let p of n.aliases.concat(n.version))if(new RegExp(c,"i").test(p)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ie("outdated"))s.hidden=!1})}function Ka(e,{worker$:t}){let{searchParams:r}=ve();r.has("q")&&(Be("search",!0),e.value=r.get("q"),e.focus(),We("search").pipe(He(i=>!i)).subscribe(()=>{let i=ve();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=vt(e),n=T(t.pipe(He(Ht)),d(e,"keyup"),o).pipe(m(()=>e.value),Y());return Q([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),B(1))}function Jn(e,{worker$:t}){let r=new g,o=r.pipe(ee(),oe(!0));Q([t.pipe(He(Ht)),r],(i,a)=>a).pipe(X("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(X("focus")).subscribe(({focus:i})=>{i&&Be("search",i)}),d(e.form,"reset").pipe(U(o)).subscribe(()=>e.focus());let n=P("header [for=__search]");return d(n,"click").subscribe(()=>e.focus()),Ka(e,{worker$:t}).pipe(y(i=>r.next(i)),_(()=>r.complete()),m(i=>F({ref:e},i)),B(1))}function Xn(e,{worker$:t,query$:r}){let o=new g,n=Yo(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=P(":scope > :first-child",e),s=P(":scope > :last-child",e);We("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(ne(r),Ir(t.pipe(He(Ht)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?ge("search.result.none"):ge("search.result.placeholder");break;case 1:a.textContent=ge("search.result.one");break;default:let u=ar(l.length);a.textContent=ge("search.result.other",u)}});let c=o.pipe(y(()=>s.innerHTML=""),v(({items:l})=>T($(...l.slice(0,10)),$(...l.slice(10)).pipe(Ke(4),jr(n),v(([f])=>f)))),m(hn),le());return c.subscribe(l=>s.appendChild(l)),c.pipe(re(l=>{let f=me("details",l);return typeof f=="undefined"?L:d(f,"toggle").pipe(U(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(fr),m(({data:l})=>l)).pipe(y(l=>o.next(l)),_(()=>o.complete()),m(l=>F({ref:e},l)))}function Qa(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ve();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Zn(e,t){let r=new g,o=r.pipe(ee(),oe(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),d(e,"click").pipe(U(o)).subscribe(n=>n.preventDefault()),Qa(e,t).pipe(y(n=>r.next(n)),_(()=>r.complete()),m(n=>F({ref:e},n)))}function ei(e,{worker$:t,keyboard$:r}){let o=new g,n=Te("search-query"),i=T(d(n,"keydown"),d(n,"focus")).pipe(Oe(ae),m(()=>n.value),Y());return o.pipe(je(i),m(([{suggest:s},c])=>{let p=c.split(/([\s-]+)/);if(s!=null&&s.length&&p[p.length-1]){let l=s[s.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(fr),m(({data:s})=>s)).pipe(y(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ti(e,{index$:t,keyboard$:r}){let o=we();try{let n=Bn(o.search,t),i=Te("search-query",e),a=Te("search-result",e);d(e,"click").pipe(b(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>Be("search",!1)),r.pipe(b(({mode:c})=>c==="search")).subscribe(c=>{let p=Re();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of R(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,h])=>h-u);f.click()}c.claim()}break;case"Escape":case"Tab":Be("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...R(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Re()&&i.focus()}}),r.pipe(b(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let s=Jn(i,{worker$:n});return T(s,Xn(a,{worker$:n,query$:s})).pipe($e(...ie("search-share",e).map(c=>Zn(c,{query$:s})),...ie("search-suggest",e).map(c=>ei(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,qe}}function ri(e,{index$:t,location$:r}){return Q([t,r.pipe(q(ve()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>Yn(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let c=s.textContent,p=o(c);p.length>c.length&&n.set(s,p)}for(let[s,c]of n){let{childNodes:p}=E("span",null,c);s.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Ya(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return Q([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),Y((i,a)=>i.height===a.height&&i.locked===a.locked))}function Qr(e,o){var n=o,{header$:t}=n,r=to(n,["header$"]);let i=P(".md-sidebar__scrollwrap",e),{y:a}=Ue(i);return H(()=>{let s=new g,c=s.pipe(ee(),oe(!0)),p=s.pipe(Me(0,de));return p.pipe(ne(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe(He()).subscribe(()=>{for(let l of R(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:h}=pe(f);f.scrollTo({top:u-h/2})}}}),fe(R("label[tabindex]",e)).pipe(re(l=>d(l,"click").pipe(Oe(ae),m(()=>l),U(c)))).subscribe(l=>{let f=P(`[id="${l.htmlFor}"]`);P(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),Ya(e,r).pipe(y(l=>s.next(l)),_(()=>s.complete()),m(l=>F({ref:e},l)))})}function oi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Lt(De(`${r}/releases/latest`).pipe(he(()=>L),m(o=>({version:o.tag_name})),Qe({})),De(r).pipe(he(()=>L),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Qe({}))).pipe(m(([o,n])=>F(F({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return De(r).pipe(m(o=>({repositories:o.public_repos})),Qe({}))}}function ni(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return De(r).pipe(he(()=>L),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Qe({}))}function ii(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return oi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return ni(r,o)}return L}var Ba;function Ga(e){return Ba||(Ba=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return $(t);if(ie("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return L}return ii(e.href).pipe(y(o=>__md_set("__source",o,sessionStorage)))}).pipe(he(()=>L),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),B(1)))}function ai(e){let t=P(":scope > :last-child",e);return H(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(bn(o)),t.classList.add("md-source__repository--active")}),Ga(e).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))})}function Ja(e,{viewport$:t,header$:r}){return Ee(document.body).pipe(v(()=>pr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),X("hidden"))}function si(e,t){return H(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(G("navigation.tabs.sticky")?$({hidden:!1}):Ja(e,t)).pipe(y(o=>r.next(o)),_(()=>r.complete()),m(o=>F({ref:e},o)))})}function Xa(e,{viewport$:t,header$:r}){let o=new Map,n=R(".md-nav__link",e);for(let s of n){let c=decodeURIComponent(s.hash.substring(1)),p=me(`[id="${c}"]`);typeof p!="undefined"&&o.set(s,p)}let i=r.pipe(X("height"),m(({height:s})=>{let c=Te("main"),p=P(":scope > :first-child",c);return s+.8*(p.offsetTop-c.offsetTop)}),le());return Ee(document.body).pipe(X("height"),v(s=>H(()=>{let c=[];return $([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let h=f.offsetParent;for(;h;h=h.offsetParent)u+=h.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),je(i),v(([c,p])=>t.pipe(Rr(([l,f],{offset:{y:u},size:h})=>{let w=u+h.height>=Math.floor(s.height);for(;f.length;){let[,A]=f[0];if(A-p=u&&!w)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),Y((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,c])=>({prev:s.map(([p])=>p),next:c.map(([p])=>p)})),q({prev:[],next:[]}),Ke(2,1),m(([s,c])=>s.prev.length{let i=new g,a=i.pipe(ee(),oe(!0));if(i.subscribe(({prev:s,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===s.length-1)}),G("toc.follow")){let s=T(t.pipe(be(1),m(()=>{})),t.pipe(be(250),m(()=>"smooth")));i.pipe(b(({prev:c})=>c.length>0),je(o.pipe(Oe(ae))),ne(s)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=sr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:h}=pe(f);f.scrollTo({top:u-h/2,behavior:p})}}})}return G("navigation.tracking")&&t.pipe(U(a),X("offset"),be(250),Le(1),U(n.pipe(Le(1))),at({delay:250}),ne(i)).subscribe(([,{prev:s}])=>{let c=ve(),p=s[s.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Xa(e,{viewport$:t,header$:r}).pipe(y(s=>i.next(s)),_(()=>i.complete()),m(s=>F({ref:e},s)))})}function Za(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Ke(2,1),m(([a,s])=>a>s&&s>0),Y()),i=r.pipe(m(({active:a})=>a));return Q([i,n]).pipe(m(([a,s])=>!(a&&s)),Y(),U(o.pipe(Le(1))),oe(!0),at({delay:250}),m(a=>({hidden:a})))}function pi(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(ee(),oe(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(U(a),X("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),d(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),Za(e,{viewport$:t,main$:o,target$:n}).pipe(y(s=>i.next(s)),_(()=>i.complete()),m(s=>F({ref:e},s)))}function li({document$:e}){e.pipe(v(()=>R(".md-ellipsis")),re(t=>yt(t).pipe(U(e.pipe(Le(1))),b(r=>r),m(()=>t),ye(1))),b(t=>t.offsetWidth{let r=t.innerText,o=t.closest("a")||t;return o.title=r,Ge(o).pipe(U(e.pipe(Le(1))),_(()=>o.removeAttribute("title")))})).subscribe(),e.pipe(v(()=>R(".md-status")),re(t=>Ge(t))).subscribe()}function mi({document$:e,tablet$:t}){e.pipe(v(()=>R(".md-toggle--indeterminate")),y(r=>{r.indeterminate=!0,r.checked=!1}),re(r=>d(r,"change").pipe(Fr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),ne(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function es(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function fi({document$:e}){e.pipe(v(()=>R("[data-md-scrollfix]")),y(t=>t.removeAttribute("data-md-scrollfix")),b(es),re(t=>d(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function ui({viewport$:e,tablet$:t}){Q([We("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>$(r).pipe(Ye(r?400:100))),ne(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ts(){return location.protocol==="file:"?gt(`${new URL("search/search_index.js",Yr.base)}`).pipe(m(()=>__index),B(1)):De(new URL("search/search_index.json",Yr.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var rt=No(),Rt=Jo(),wt=en(Rt),Br=Go(),_e=pn(),ur=At("(min-width: 960px)"),hi=At("(min-width: 1220px)"),bi=tn(),Yr=we(),vi=document.forms.namedItem("search")?ts():qe,Gr=new g;Wn({alert$:Gr});var Jr=new g;G("navigation.instant")&&zn({location$:Rt,viewport$:_e,progress$:Jr}).subscribe(rt);var di;((di=Yr.version)==null?void 0:di.provider)==="mike"&&Gn({document$:rt});T(Rt,wt).pipe(Ye(125)).subscribe(()=>{Be("drawer",!1),Be("search",!1)});Br.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=me("link[rel=prev]");typeof t!="undefined"&&st(t);break;case"n":case".":let r=me("link[rel=next]");typeof r!="undefined"&&st(r);break;case"Enter":let o=Re();o instanceof HTMLLabelElement&&o.click()}});li({document$:rt});mi({document$:rt,tablet$:ur});fi({document$:rt});ui({viewport$:_e,tablet$:ur});var tt=Rn(Te("header"),{viewport$:_e}),$t=rt.pipe(m(()=>Te("main")),v(e=>Fn(e,{viewport$:_e,header$:tt})),B(1)),rs=T(...ie("consent").map(e=>fn(e,{target$:wt})),...ie("dialog").map(e=>$n(e,{alert$:Gr})),...ie("header").map(e=>Pn(e,{viewport$:_e,header$:tt,main$:$t})),...ie("palette").map(e=>jn(e)),...ie("progress").map(e=>Un(e,{progress$:Jr})),...ie("search").map(e=>ti(e,{index$:vi,keyboard$:Br})),...ie("source").map(e=>ai(e))),os=H(()=>T(...ie("announce").map(e=>mn(e)),...ie("content").map(e=>Hn(e,{viewport$:_e,target$:wt,print$:bi})),...ie("content").map(e=>G("search.highlight")?ri(e,{index$:vi,location$:Rt}):L),...ie("header-title").map(e=>In(e,{viewport$:_e,header$:tt})),...ie("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Ur(hi,()=>Qr(e,{viewport$:_e,header$:tt,main$:$t})):Ur(ur,()=>Qr(e,{viewport$:_e,header$:tt,main$:$t}))),...ie("tabs").map(e=>si(e,{viewport$:_e,header$:tt})),...ie("toc").map(e=>ci(e,{viewport$:_e,header$:tt,main$:$t,target$:wt})),...ie("top").map(e=>pi(e,{viewport$:_e,header$:tt,main$:$t,target$:wt})))),gi=rt.pipe(v(()=>os),$e(rs),B(1));gi.subscribe();window.document$=rt;window.location$=Rt;window.target$=wt;window.keyboard$=Br;window.viewport$=_e;window.tablet$=ur;window.screen$=hi;window.print$=bi;window.alert$=Gr;window.progress$=Jr;window.component$=gi;})(); +//# sourceMappingURL=bundle.1e8ae164.min.js.map + diff --git a/assets/javascripts/bundle.1e8ae164.min.js.map b/assets/javascripts/bundle.1e8ae164.min.js.map new file mode 100644 index 000000000..6c33b8e8e --- /dev/null +++ b/assets/javascripts/bundle.1e8ae164.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/rxjs/node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/relations_guide/index.html b/relations_guide/index.html new file mode 100644 index 000000000..f789ae233 --- /dev/null +++ b/relations_guide/index.html @@ -0,0 +1,1770 @@ + + + + + + + + + + + + + + + + + + + + + + + CL relations - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + +

Cell Ontology (CL) relations guide.

+

Intro

+

The aim of this document is to provide an accessible guide to how to use relations to record the properties that define cell types including location, lineage, function, morphology and marker genes. The term 'relations' here refers principally to OWL object properties, but also includes annoation properties used as shortcuts for more expressive logical axioms that can be programatically generated from them.

+

Relations in this guide are grouped by general use case (e.g. recording location) and each is illustrated by an example e.g.-

+
    +
  • melanocyte subClassOf ‘has part’ some melanosome.
  • +
+

This should be read as ‘all melanocytes have some type of melanosome as a part’ as should all axioms of this form. The examples should all be correct, but may not reflect the full complexity of axioms in the ontology. Where no example is currently present in CL, examples are taken from the Drosophila Anatomy Ontology, which follows the same schema.

+

Recording location

+

Location of cell types is recorded by relating a cell type to a term in an anatomical ontology. For the Cell Ontology this means a term from Uberon.

+

'part of'

+

Use part_of for cases where the location is a material anatomical structure (rather than a space, such as a sinus) and all of the cell is within the anatomical structure.

+

epithelial cell' +subClassOf 'part of' +some epithelium

+

‘part of’ is transitive, which means that it applies across chains of relationships. For example,

+

‘ileal goblet cell’ part_of some ileum

+

ilium ‘part of’ some ‘small intestine’

+

‘small intestine’ ‘part of’ some intestine

+

=>

+

’ileal goblet cell’ ‘part of’ some ‘small intestine’ +& +‘ileal goblet cell’ ‘part of’ some intestine

+

located_in

+

To record the location of a cell in an anatomical space (e.g., a sinus), +'located in' is used.

+

For example:

+

lymph node marginal reticular cell’ subClassOf 'located in' some 'subcapsular sinus of lymph node'

+

overlaps

+

'part of' applies in cases where an entire cell is within an anatomical structure, but some cells have parts in multiple anatomical structures. For example, many neurons span multiple regions of the central nervous system. The general relation for this is overlaps (has some part in).

+

overlaps is not currently used directly in the cell ontology (time of writing 05/2023), but more specific relationships exist for recording the location of neurons and their parts. These are described in the next section.

+

Recording the location of neurons

+

has soma location

+

When neurobiologists talk about the location of vertebrate neurons, they are typically referring to soma location. The importance of soma location to identify is underscored by how commonly cell types are named, in part, by soma location. We therefore have a dedicated relation for recording this: 'has soma location'.

+

For example, anterior horn motor neuron has the following subclass axiom:

+

'has soma location' some 'ventral horn of spinal cord'

+

axiomatization of ‘has soma location’

+
    +
  • +

    subPropertyOf: overlaps # if X has_soma_location some Y, then X overlaps some Y)

    +
  • +
  • +

    domain: neuron # X has_soma_location some Y => X is inferred to be a subClassOf neuron

    +
  • +
  • +

    property chain: has_soma_location o part_of --> has_soma_location # If x has soma location y and y is part_of z, then x has_soma_location_z

    +
  • +
+

Example of reasoning with the property chain:

+

'cortical interneuron' equivalentTo 'interneuron' that has_soma_location some 'cerebral cortex'

+

'rosehip neuron' subClassOf interneuron and has_soma_location some 'cortical layer 1'

+

'cortical layer 1' subClassOf part_of some 'cerebral cortex

+

=> 'rosehip neuron' subClassOf 'cortical interneuron'

+

sends synaptic output to region

+

A relationship between a neuron and a region, where the neuron has a functionally relevant number of output synapses in that region.

+

'adult basket subesophageal neuron' SubClassOf sends synaptic output to region +some inferior posterior slope

+

receives synaptic input in region

+

A relationship between a neuron and a region, where the neuron has a functionally relevant number of input synapses:

+

e.g. 'adult basket subesophageal neuron' SubClassOf ‘receives synaptic input in region’ +some ‘superior posterior slope

+

fasciculates_with

+

Use this to record the tracts or nerves that a neuron’s projections fasciculate with.

+

e.g. ‘Betz cell’ subClasssOf ‘fasciculates with’ some ‘corticospinal tract’.

+

subPropertyOf: overlaps

+

domain: neuron

+

range: neuron projection bundle

+

Recording synaptic connectivity (neurons)

+

To record neuron-to-neuron or motor neuron-to-target muscle connectivity, consider the following object properties. These properties should be used when connectivity is key to the definition, for example, in cases where a motor neuron type is defined by the type of muscle fiber on which it synapses.

+

synapsed to

+

For example, 'alpha motor neuron' SubClassOf synapsed to some 'extrafusal muscle fiber'

+

synapsed by

+ +

For example, 'extrafusal muscle fiber' SubClassOf synapsed by some 'alpha motor neuron'

+

Recording function

+

Cellular function is recorded by linking GO biological process terms with the object properties 'capable of' and ‘capable of part of’

+

'capable of'

+

Use this relation where the cell is capable of carrying out the entirety of the process

+

For example, 'hilus cell of ovary' has the following subclass:

+

'capable of' some 'androgen secretion'

+

Recording neurotransmitter for neurons

+

To record which neurotransmitter a neuron releases, use a 'capable of' relation to link the neuron to a subclass of GO neurotransmitter secretion that references a neurotransmitter type. This should be sufficient for autoclassification.

+

e.g. 'medium spiny neuron' 'capable of' some 'gamma-aminobutyric acid secretion, neurotransmission'

+

‘capable of part of’

+

Use this relationship where only part of the process occurs in the cell type.

+

e.g. 'retinal bipolar neuron' 'capable of part of' some 'visual perception'

+

Recording developmental lineage

+

Developmental lineage is recorded between cell types with the object property develops from (a transitive property), or in the case where there are no intermediates between the cells, 'directly develops from' (a non-transitive subproperty of develops_from)

+

For example, 'leukocyte' subClassOf develops from some 'hematopoietic stem cell'

+

Recording cell markers

+

Only markers that are necessary to define a cell type should be recorded.

+

cell surface (protein) markers

+

The cell ontology has a set of terms for recording cell surface markers.

+

The most commonly used relation for recording markers is 'has plasma +membrane part'. This +object property is used to record cell surface markers, especially in +immune cells. There are also more specific properties, 'has low +plasma membrane amount' +and 'has high plasma membrane +amount', that can be used +at an editor's discretion. In each case, a term from the PRotein +Ontology (PRO) or a +protein complex term from the Gene Ontology +(GO) is used as the object +of the relation.

+

For example, 'alpha-beta T cell' has the following +equivalence axiom:

+

'T cell' and 'has +plasma membrane part' +some 'alpha-beta T cell receptor complex'

+

Absence of a marker can be recorded using +lacks_plasma_membrane_part

+

Warning - this is used in place of the more accurate OWL expression "NOT has_part some X*** in order to keep within the EL profile of OWL. It's use with a general class as a target can potentially lead to reasoning errors.

+

recording gene markers

+

‘expresses’

+

Use this to link a cell type to a gene or gene product that defines it:

+

For example:

+

'lamp5 GABAergic cortical interneuron' EquivalentTo: +interneuron and ('has soma location' some 'cerebral cortex') +and ('capable of' some 'gamma-aminobutyric acid secretion, neurotransmission') +and (expresses some 'lysosome-associated membrane glycoprotein 5')

+

In FBbt, FlyBase Gene IDs are permitted here (using standard resolvable URL pattern). In CL currenly only PRO IDs are permitted. In PCL, a broader range of IDs have been used (depending on data sources used).

+

Recording cell parts

+

To record parts above the granularity of proteins and complexes, use a 'has part' relationship with an object from the Gene Ontology cellular_component branch.

+

e.g. 'melanocyte' subClassOf 'has part' some 'melanosome'

+

This GO term can be combined with a PATO quality term (e.g. for shape) where necessary, e.g.

+

For example:

+

'mature basophil' subClassOf ('has part' some (nucleus and ('has characteristic' some lobed)))

+

Recording general cellular characteristics

+

The ontology PATO, +has a rich set of terms that can be used to record the general +characteristics of cells, such as their morphology. These are recorded +using 'has characteristic'.

+

In choosing PATO terms, avoid those referring to some change in +characteristic (e.g,.’ increased branchiness’). The following list of +examples is not exhaustive:

+

Recording Morphology

+

PATO has a set of general morphology terms which may be applicable to +cells.

+

For example, erythrocyte +subClassOf 'has characteristic' +some biconcave

+

PATO also has a set of terms for specific cell morphologies (mostly +neuronal), e.g.

+

‘Betz cell’ subClassOf ‘has characteristic’ some ‘standard pyramidal morphology’

+

Recording nuclear number

+

To record the number of nuclei in a cell, use a PATO subclass +under the term 'nucleate quality' with the 'has +characteristic' relation.

+

image

+

For example,

+

platelet subClassOf ('has_characteristic' some anucleate)

+

Note - that PATO includes bridging axioms that infer part relationships +based on these characteristics.

+

e.g.

+

cell and ('has characteristic' some multinucleate) SubClassOf 'has part' +some nucleus

+

Taxon constraints

+

See +https://oboacademy.github.io/obook/explanation/taxon-constraints-explainer/.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/resolving_merge_conflicts/index.html b/resolving_merge_conflicts/index.html new file mode 100644 index 000000000..00d093a2b --- /dev/null +++ b/resolving_merge_conflicts/index.html @@ -0,0 +1,1184 @@ + + + + + + + + + + + + + + + + + + + + + + + Resolving merge conflicts - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Resolving merge conflicts

+ +

A guide to resolving merge conflicts

+

When you come to merge your pull request, you may find that conflicts prevent automated merging back into the master. In some cases, GitHub supports resolution of these through its web interface. However, probably due to file size, this is not currently supported for cl-edit.owl.

+

The majority of the time, the conflict is trivial - due to addition of new terms to the same point in the file. Because terms are ordered in the file by ID, this happens whenever two edits add terms without any intervening IDs. Trivial clashes are easy to spot - they involve whole term stanzas + declarations.

+

Occassionally non-trivial clashes will happen when two pull requests include edits to the same term or even the same axiom. Ask an editor for help if you don't feel confident resolving these.

+

SOP.

+
    +
  1. +

    Reserialise the Master file using the Ontology Development Kit (ODK). This requires setting up Docker and ODK. If not already set up, follow the instructions here.

    +
  2. +
  3. +

    Open Docker.

    +
  4. +
  5. +

    At the line command (PC) or Terminal (Mac), use the cd (change directory) command to navigate to the repository's src/ontology/ directory. + For example,

    +
  6. +
+

''' + cd PATH_TO_ONTOLOGY/src/ontology/ + '''

+

Replace "PATH_TO_ONTOLOGY" with the actual file path to the ontology. If you need to orient yourself, use the '''pwd''' (present working directory) or '''ls''' (list) line commands.

+
    +
  1. If you are resolving a conflict in an .owl file, run:
  2. +
+

''' +sh run.sh make normalize_src + '''

+

If you are resolving a conflict in an .obo file, run:

+

''' +sh run.sh make normalize_obo_src + '''

+
    +
  1. +

    In CL, edits sometimes result in creating a large amount of uninteded differences involving ^^xsd:string. If you see these differences after running the command above, they can be resolved by following the instructions here.

    +
  2. +
  3. +

    Resolving conflicts in GitHub Desktop and VSCode:

    +
  4. +
+
+

1) Update Branches in GitHub Desktop:

+
    +
  • Checkout Master and pull to make sure your Master branch is up to date.
  • +
  • Checkout the branch for the pull request and make sure it is up to date.
  • +
  • +

    Choose Branch > Update from Master to integrate the latest changes from the master branch.

    +

    Update from Master

    +
  • +
  • +

    GitHub Desktop will detect the clash and suggest opening it in your text editor of choice (e.g., Atom, VSCode).

    +
  • +
+

2) Resolve Conflicts in VSCode:

+
    +
  • Open the conflicting file in VSCode.
  • +
  • +

    VSCode will highlight the conflicting areas with markers:

    +

    image

    +
  • +
  • +

    For trivial ordering problems, either manually delete the conflict markers (<<<<<<<, =======, >>>>>>>) and ensure all necessary declarations are retained, or click on Accept Both Changes. This will automatically merge all term declarations from both the HEAD and incoming changes, removing the conflict markers.

    +
  • +
  • Important - Always check that the change does look trivial before making any changes.
  • +
+

3) Reserialise OWL Files:

+
    +
  • +

    It is essential to reserialise after resolving conflicts to ensure consistency and proper formatting:

    +

    sh run.sh make normalize_src

    +
  • +
+

4) Complete the Resolution Process:

+
    +
  • In GitHub Desktop, review the changes, commit the resolved conflict, and push the updates back to GitHub.
  • +
  • Check the resulting diffs on the Pull Request on GitHub.
  • +
  • Once the checks have run and are successful, merge and delete the branch.
  • +
+
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/search/lunr.js b/search/lunr.js new file mode 100644 index 000000000..aca0a167f --- /dev/null +++ b/search/lunr.js @@ -0,0 +1,3475 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */ + +;(function(){ + +/** + * A convenience function for configuring and constructing + * a new lunr Index. + * + * A lunr.Builder instance is created and the pipeline setup + * with a trimmer, stop word filter and stemmer. + * + * This builder object is yielded to the configuration function + * that is passed as a parameter, allowing the list of fields + * and other builder parameters to be customised. + * + * All documents _must_ be added within the passed config function. + * + * @example + * var idx = lunr(function () { + * this.field('title') + * this.field('body') + * this.ref('id') + * + * documents.forEach(function (doc) { + * this.add(doc) + * }, this) + * }) + * + * @see {@link lunr.Builder} + * @see {@link lunr.Pipeline} + * @see {@link lunr.trimmer} + * @see {@link lunr.stopWordFilter} + * @see {@link lunr.stemmer} + * @namespace {function} lunr + */ +var lunr = function (config) { + var builder = new lunr.Builder + + builder.pipeline.add( + lunr.trimmer, + lunr.stopWordFilter, + lunr.stemmer + ) + + builder.searchPipeline.add( + lunr.stemmer + ) + + config.call(builder, builder) + return builder.build() +} + +lunr.version = "2.3.9" +/*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A namespace containing utils for the rest of the lunr library + * @namespace lunr.utils + */ +lunr.utils = {} + +/** + * Print a warning message to the console. + * + * @param {String} message The message to be printed. + * @memberOf lunr.utils + * @function + */ +lunr.utils.warn = (function (global) { + /* eslint-disable no-console */ + return function (message) { + if (global.console && console.warn) { + console.warn(message) + } + } + /* eslint-enable no-console */ +})(this) + +/** + * Convert an object to a string. + * + * In the case of `null` and `undefined` the function returns + * the empty string, in all other cases the result of calling + * `toString` on the passed object is returned. + * + * @param {Any} obj The object to convert to a string. + * @return {String} string representation of the passed object. + * @memberOf lunr.utils + */ +lunr.utils.asString = function (obj) { + if (obj === void 0 || obj === null) { + return "" + } else { + return obj.toString() + } +} + +/** + * Clones an object. + * + * Will create a copy of an existing object such that any mutations + * on the copy cannot affect the original. + * + * Only shallow objects are supported, passing a nested object to this + * function will cause a TypeError. + * + * Objects with primitives, and arrays of primitives are supported. + * + * @param {Object} obj The object to clone. + * @return {Object} a clone of the passed object. + * @throws {TypeError} when a nested object is passed. + * @memberOf Utils + */ +lunr.utils.clone = function (obj) { + if (obj === null || obj === undefined) { + return obj + } + + var clone = Object.create(null), + keys = Object.keys(obj) + + for (var i = 0; i < keys.length; i++) { + var key = keys[i], + val = obj[key] + + if (Array.isArray(val)) { + clone[key] = val.slice() + continue + } + + if (typeof val === 'string' || + typeof val === 'number' || + typeof val === 'boolean') { + clone[key] = val + continue + } + + throw new TypeError("clone is not deep and does not support nested objects") + } + + return clone +} +lunr.FieldRef = function (docRef, fieldName, stringValue) { + this.docRef = docRef + this.fieldName = fieldName + this._stringValue = stringValue +} + +lunr.FieldRef.joiner = "/" + +lunr.FieldRef.fromString = function (s) { + var n = s.indexOf(lunr.FieldRef.joiner) + + if (n === -1) { + throw "malformed field ref string" + } + + var fieldRef = s.slice(0, n), + docRef = s.slice(n + 1) + + return new lunr.FieldRef (docRef, fieldRef, s) +} + +lunr.FieldRef.prototype.toString = function () { + if (this._stringValue == undefined) { + this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef + } + + return this._stringValue +} +/*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A lunr set. + * + * @constructor + */ +lunr.Set = function (elements) { + this.elements = Object.create(null) + + if (elements) { + this.length = elements.length + + for (var i = 0; i < this.length; i++) { + this.elements[elements[i]] = true + } + } else { + this.length = 0 + } +} + +/** + * A complete set that contains all elements. + * + * @static + * @readonly + * @type {lunr.Set} + */ +lunr.Set.complete = { + intersect: function (other) { + return other + }, + + union: function () { + return this + }, + + contains: function () { + return true + } +} + +/** + * An empty set that contains no elements. + * + * @static + * @readonly + * @type {lunr.Set} + */ +lunr.Set.empty = { + intersect: function () { + return this + }, + + union: function (other) { + return other + }, + + contains: function () { + return false + } +} + +/** + * Returns true if this set contains the specified object. + * + * @param {object} object - Object whose presence in this set is to be tested. + * @returns {boolean} - True if this set contains the specified object. + */ +lunr.Set.prototype.contains = function (object) { + return !!this.elements[object] +} + +/** + * Returns a new set containing only the elements that are present in both + * this set and the specified set. + * + * @param {lunr.Set} other - set to intersect with this set. + * @returns {lunr.Set} a new set that is the intersection of this and the specified set. + */ + +lunr.Set.prototype.intersect = function (other) { + var a, b, elements, intersection = [] + + if (other === lunr.Set.complete) { + return this + } + + if (other === lunr.Set.empty) { + return other + } + + if (this.length < other.length) { + a = this + b = other + } else { + a = other + b = this + } + + elements = Object.keys(a.elements) + + for (var i = 0; i < elements.length; i++) { + var element = elements[i] + if (element in b.elements) { + intersection.push(element) + } + } + + return new lunr.Set (intersection) +} + +/** + * Returns a new set combining the elements of this and the specified set. + * + * @param {lunr.Set} other - set to union with this set. + * @return {lunr.Set} a new set that is the union of this and the specified set. + */ + +lunr.Set.prototype.union = function (other) { + if (other === lunr.Set.complete) { + return lunr.Set.complete + } + + if (other === lunr.Set.empty) { + return this + } + + return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements))) +} +/** + * A function to calculate the inverse document frequency for + * a posting. This is shared between the builder and the index + * + * @private + * @param {object} posting - The posting for a given term + * @param {number} documentCount - The total number of documents. + */ +lunr.idf = function (posting, documentCount) { + var documentsWithTerm = 0 + + for (var fieldName in posting) { + if (fieldName == '_index') continue // Ignore the term index, its not a field + documentsWithTerm += Object.keys(posting[fieldName]).length + } + + var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5) + + return Math.log(1 + Math.abs(x)) +} + +/** + * A token wraps a string representation of a token + * as it is passed through the text processing pipeline. + * + * @constructor + * @param {string} [str=''] - The string token being wrapped. + * @param {object} [metadata={}] - Metadata associated with this token. + */ +lunr.Token = function (str, metadata) { + this.str = str || "" + this.metadata = metadata || {} +} + +/** + * Returns the token string that is being wrapped by this object. + * + * @returns {string} + */ +lunr.Token.prototype.toString = function () { + return this.str +} + +/** + * A token update function is used when updating or optionally + * when cloning a token. + * + * @callback lunr.Token~updateFunction + * @param {string} str - The string representation of the token. + * @param {Object} metadata - All metadata associated with this token. + */ + +/** + * Applies the given function to the wrapped string token. + * + * @example + * token.update(function (str, metadata) { + * return str.toUpperCase() + * }) + * + * @param {lunr.Token~updateFunction} fn - A function to apply to the token string. + * @returns {lunr.Token} + */ +lunr.Token.prototype.update = function (fn) { + this.str = fn(this.str, this.metadata) + return this +} + +/** + * Creates a clone of this token. Optionally a function can be + * applied to the cloned token. + * + * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token. + * @returns {lunr.Token} + */ +lunr.Token.prototype.clone = function (fn) { + fn = fn || function (s) { return s } + return new lunr.Token (fn(this.str, this.metadata), this.metadata) +} +/*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A function for splitting a string into tokens ready to be inserted into + * the search index. Uses `lunr.tokenizer.separator` to split strings, change + * the value of this property to change how strings are split into tokens. + * + * This tokenizer will convert its parameter to a string by calling `toString` and + * then will split this string on the character in `lunr.tokenizer.separator`. + * Arrays will have their elements converted to strings and wrapped in a lunr.Token. + * + * Optional metadata can be passed to the tokenizer, this metadata will be cloned and + * added as metadata to every token that is created from the object to be tokenized. + * + * @static + * @param {?(string|object|object[])} obj - The object to convert into tokens + * @param {?object} metadata - Optional metadata to associate with every token + * @returns {lunr.Token[]} + * @see {@link lunr.Pipeline} + */ +lunr.tokenizer = function (obj, metadata) { + if (obj == null || obj == undefined) { + return [] + } + + if (Array.isArray(obj)) { + return obj.map(function (t) { + return new lunr.Token( + lunr.utils.asString(t).toLowerCase(), + lunr.utils.clone(metadata) + ) + }) + } + + var str = obj.toString().toLowerCase(), + len = str.length, + tokens = [] + + for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) { + var char = str.charAt(sliceEnd), + sliceLength = sliceEnd - sliceStart + + if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) { + + if (sliceLength > 0) { + var tokenMetadata = lunr.utils.clone(metadata) || {} + tokenMetadata["position"] = [sliceStart, sliceLength] + tokenMetadata["index"] = tokens.length + + tokens.push( + new lunr.Token ( + str.slice(sliceStart, sliceEnd), + tokenMetadata + ) + ) + } + + sliceStart = sliceEnd + 1 + } + + } + + return tokens +} + +/** + * The separator used to split a string into tokens. Override this property to change the behaviour of + * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens. + * + * @static + * @see lunr.tokenizer + */ +lunr.tokenizer.separator = /[\s\-]+/ +/*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.Pipelines maintain an ordered list of functions to be applied to all + * tokens in documents entering the search index and queries being ran against + * the index. + * + * An instance of lunr.Index created with the lunr shortcut will contain a + * pipeline with a stop word filter and an English language stemmer. Extra + * functions can be added before or after either of these functions or these + * default functions can be removed. + * + * When run the pipeline will call each function in turn, passing a token, the + * index of that token in the original list of all tokens and finally a list of + * all the original tokens. + * + * The output of functions in the pipeline will be passed to the next function + * in the pipeline. To exclude a token from entering the index the function + * should return undefined, the rest of the pipeline will not be called with + * this token. + * + * For serialisation of pipelines to work, all functions used in an instance of + * a pipeline should be registered with lunr.Pipeline. Registered functions can + * then be loaded. If trying to load a serialised pipeline that uses functions + * that are not registered an error will be thrown. + * + * If not planning on serialising the pipeline then registering pipeline functions + * is not necessary. + * + * @constructor + */ +lunr.Pipeline = function () { + this._stack = [] +} + +lunr.Pipeline.registeredFunctions = Object.create(null) + +/** + * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token + * string as well as all known metadata. A pipeline function can mutate the token string + * or mutate (or add) metadata for a given token. + * + * A pipeline function can indicate that the passed token should be discarded by returning + * null, undefined or an empty string. This token will not be passed to any downstream pipeline + * functions and will not be added to the index. + * + * Multiple tokens can be returned by returning an array of tokens. Each token will be passed + * to any downstream pipeline functions and all will returned tokens will be added to the index. + * + * Any number of pipeline functions may be chained together using a lunr.Pipeline. + * + * @interface lunr.PipelineFunction + * @param {lunr.Token} token - A token from the document being processed. + * @param {number} i - The index of this token in the complete list of tokens for this document/field. + * @param {lunr.Token[]} tokens - All tokens for this document/field. + * @returns {(?lunr.Token|lunr.Token[])} + */ + +/** + * Register a function with the pipeline. + * + * Functions that are used in the pipeline should be registered if the pipeline + * needs to be serialised, or a serialised pipeline needs to be loaded. + * + * Registering a function does not add it to a pipeline, functions must still be + * added to instances of the pipeline for them to be used when running a pipeline. + * + * @param {lunr.PipelineFunction} fn - The function to check for. + * @param {String} label - The label to register this function with + */ +lunr.Pipeline.registerFunction = function (fn, label) { + if (label in this.registeredFunctions) { + lunr.utils.warn('Overwriting existing registered function: ' + label) + } + + fn.label = label + lunr.Pipeline.registeredFunctions[fn.label] = fn +} + +/** + * Warns if the function is not registered as a Pipeline function. + * + * @param {lunr.PipelineFunction} fn - The function to check for. + * @private + */ +lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { + var isRegistered = fn.label && (fn.label in this.registeredFunctions) + + if (!isRegistered) { + lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) + } +} + +/** + * Loads a previously serialised pipeline. + * + * All functions to be loaded must already be registered with lunr.Pipeline. + * If any function from the serialised data has not been registered then an + * error will be thrown. + * + * @param {Object} serialised - The serialised pipeline to load. + * @returns {lunr.Pipeline} + */ +lunr.Pipeline.load = function (serialised) { + var pipeline = new lunr.Pipeline + + serialised.forEach(function (fnName) { + var fn = lunr.Pipeline.registeredFunctions[fnName] + + if (fn) { + pipeline.add(fn) + } else { + throw new Error('Cannot load unregistered function: ' + fnName) + } + }) + + return pipeline +} + +/** + * Adds new functions to the end of the pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline. + */ +lunr.Pipeline.prototype.add = function () { + var fns = Array.prototype.slice.call(arguments) + + fns.forEach(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + this._stack.push(fn) + }, this) +} + +/** + * Adds a single function after a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. + * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. + */ +lunr.Pipeline.prototype.after = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + if (pos == -1) { + throw new Error('Cannot find existingFn') + } + + pos = pos + 1 + this._stack.splice(pos, 0, newFn) +} + +/** + * Adds a single function before a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. + * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. + */ +lunr.Pipeline.prototype.before = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + if (pos == -1) { + throw new Error('Cannot find existingFn') + } + + this._stack.splice(pos, 0, newFn) +} + +/** + * Removes a function from the pipeline. + * + * @param {lunr.PipelineFunction} fn The function to remove from the pipeline. + */ +lunr.Pipeline.prototype.remove = function (fn) { + var pos = this._stack.indexOf(fn) + if (pos == -1) { + return + } + + this._stack.splice(pos, 1) +} + +/** + * Runs the current list of functions that make up the pipeline against the + * passed tokens. + * + * @param {Array} tokens The tokens to run through the pipeline. + * @returns {Array} + */ +lunr.Pipeline.prototype.run = function (tokens) { + var stackLength = this._stack.length + + for (var i = 0; i < stackLength; i++) { + var fn = this._stack[i] + var memo = [] + + for (var j = 0; j < tokens.length; j++) { + var result = fn(tokens[j], j, tokens) + + if (result === null || result === void 0 || result === '') continue + + if (Array.isArray(result)) { + for (var k = 0; k < result.length; k++) { + memo.push(result[k]) + } + } else { + memo.push(result) + } + } + + tokens = memo + } + + return tokens +} + +/** + * Convenience method for passing a string through a pipeline and getting + * strings out. This method takes care of wrapping the passed string in a + * token and mapping the resulting tokens back to strings. + * + * @param {string} str - The string to pass through the pipeline. + * @param {?object} metadata - Optional metadata to associate with the token + * passed to the pipeline. + * @returns {string[]} + */ +lunr.Pipeline.prototype.runString = function (str, metadata) { + var token = new lunr.Token (str, metadata) + + return this.run([token]).map(function (t) { + return t.toString() + }) +} + +/** + * Resets the pipeline by removing any existing processors. + * + */ +lunr.Pipeline.prototype.reset = function () { + this._stack = [] +} + +/** + * Returns a representation of the pipeline ready for serialisation. + * + * Logs a warning if the function has not been registered. + * + * @returns {Array} + */ +lunr.Pipeline.prototype.toJSON = function () { + return this._stack.map(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + + return fn.label + }) +} +/*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A vector is used to construct the vector space of documents and queries. These + * vectors support operations to determine the similarity between two documents or + * a document and a query. + * + * Normally no parameters are required for initializing a vector, but in the case of + * loading a previously dumped vector the raw elements can be provided to the constructor. + * + * For performance reasons vectors are implemented with a flat array, where an elements + * index is immediately followed by its value. E.g. [index, value, index, value]. This + * allows the underlying array to be as sparse as possible and still offer decent + * performance when being used for vector calculations. + * + * @constructor + * @param {Number[]} [elements] - The flat list of element index and element value pairs. + */ +lunr.Vector = function (elements) { + this._magnitude = 0 + this.elements = elements || [] +} + + +/** + * Calculates the position within the vector to insert a given index. + * + * This is used internally by insert and upsert. If there are duplicate indexes then + * the position is returned as if the value for that index were to be updated, but it + * is the callers responsibility to check whether there is a duplicate at that index + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @returns {Number} + */ +lunr.Vector.prototype.positionForIndex = function (index) { + // For an empty vector the tuple can be inserted at the beginning + if (this.elements.length == 0) { + return 0 + } + + var start = 0, + end = this.elements.length / 2, + sliceLength = end - start, + pivotPoint = Math.floor(sliceLength / 2), + pivotIndex = this.elements[pivotPoint * 2] + + while (sliceLength > 1) { + if (pivotIndex < index) { + start = pivotPoint + } + + if (pivotIndex > index) { + end = pivotPoint + } + + if (pivotIndex == index) { + break + } + + sliceLength = end - start + pivotPoint = start + Math.floor(sliceLength / 2) + pivotIndex = this.elements[pivotPoint * 2] + } + + if (pivotIndex == index) { + return pivotPoint * 2 + } + + if (pivotIndex > index) { + return pivotPoint * 2 + } + + if (pivotIndex < index) { + return (pivotPoint + 1) * 2 + } +} + +/** + * Inserts an element at an index within the vector. + * + * Does not allow duplicates, will throw an error if there is already an entry + * for this index. + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @param {Number} val - The value to be inserted into the vector. + */ +lunr.Vector.prototype.insert = function (insertIdx, val) { + this.upsert(insertIdx, val, function () { + throw "duplicate index" + }) +} + +/** + * Inserts or updates an existing index within the vector. + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @param {Number} val - The value to be inserted into the vector. + * @param {function} fn - A function that is called for updates, the existing value and the + * requested value are passed as arguments + */ +lunr.Vector.prototype.upsert = function (insertIdx, val, fn) { + this._magnitude = 0 + var position = this.positionForIndex(insertIdx) + + if (this.elements[position] == insertIdx) { + this.elements[position + 1] = fn(this.elements[position + 1], val) + } else { + this.elements.splice(position, 0, insertIdx, val) + } +} + +/** + * Calculates the magnitude of this vector. + * + * @returns {Number} + */ +lunr.Vector.prototype.magnitude = function () { + if (this._magnitude) return this._magnitude + + var sumOfSquares = 0, + elementsLength = this.elements.length + + for (var i = 1; i < elementsLength; i += 2) { + var val = this.elements[i] + sumOfSquares += val * val + } + + return this._magnitude = Math.sqrt(sumOfSquares) +} + +/** + * Calculates the dot product of this vector and another vector. + * + * @param {lunr.Vector} otherVector - The vector to compute the dot product with. + * @returns {Number} + */ +lunr.Vector.prototype.dot = function (otherVector) { + var dotProduct = 0, + a = this.elements, b = otherVector.elements, + aLen = a.length, bLen = b.length, + aVal = 0, bVal = 0, + i = 0, j = 0 + + while (i < aLen && j < bLen) { + aVal = a[i], bVal = b[j] + if (aVal < bVal) { + i += 2 + } else if (aVal > bVal) { + j += 2 + } else if (aVal == bVal) { + dotProduct += a[i + 1] * b[j + 1] + i += 2 + j += 2 + } + } + + return dotProduct +} + +/** + * Calculates the similarity between this vector and another vector. + * + * @param {lunr.Vector} otherVector - The other vector to calculate the + * similarity with. + * @returns {Number} + */ +lunr.Vector.prototype.similarity = function (otherVector) { + return this.dot(otherVector) / this.magnitude() || 0 +} + +/** + * Converts the vector to an array of the elements within the vector. + * + * @returns {Number[]} + */ +lunr.Vector.prototype.toArray = function () { + var output = new Array (this.elements.length / 2) + + for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) { + output[j] = this.elements[i] + } + + return output +} + +/** + * A JSON serializable representation of the vector. + * + * @returns {Number[]} + */ +lunr.Vector.prototype.toJSON = function () { + return this.elements +} +/* eslint-disable */ +/*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ + +/** + * lunr.stemmer is an english language stemmer, this is a JavaScript + * implementation of the PorterStemmer taken from http://tartarus.org/~martin + * + * @static + * @implements {lunr.PipelineFunction} + * @param {lunr.Token} token - The string to stem + * @returns {lunr.Token} + * @see {@link lunr.Pipeline} + * @function + */ +lunr.stemmer = (function(){ + var step2list = { + "ational" : "ate", + "tional" : "tion", + "enci" : "ence", + "anci" : "ance", + "izer" : "ize", + "bli" : "ble", + "alli" : "al", + "entli" : "ent", + "eli" : "e", + "ousli" : "ous", + "ization" : "ize", + "ation" : "ate", + "ator" : "ate", + "alism" : "al", + "iveness" : "ive", + "fulness" : "ful", + "ousness" : "ous", + "aliti" : "al", + "iviti" : "ive", + "biliti" : "ble", + "logi" : "log" + }, + + step3list = { + "icate" : "ic", + "ative" : "", + "alize" : "al", + "iciti" : "ic", + "ical" : "ic", + "ful" : "", + "ness" : "" + }, + + c = "[^aeiou]", // consonant + v = "[aeiouy]", // vowel + C = c + "[^aeiouy]*", // consonant sequence + V = v + "[aeiou]*", // vowel sequence + + mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 + meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 + mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 + s_v = "^(" + C + ")?" + v; // vowel in stem + + var re_mgr0 = new RegExp(mgr0); + var re_mgr1 = new RegExp(mgr1); + var re_meq1 = new RegExp(meq1); + var re_s_v = new RegExp(s_v); + + var re_1a = /^(.+?)(ss|i)es$/; + var re2_1a = /^(.+?)([^s])s$/; + var re_1b = /^(.+?)eed$/; + var re2_1b = /^(.+?)(ed|ing)$/; + var re_1b_2 = /.$/; + var re2_1b_2 = /(at|bl|iz)$/; + var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$"); + var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var re_1c = /^(.+?[^aeiou])y$/; + var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + + var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + + var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + var re2_4 = /^(.+?)(s|t)(ion)$/; + + var re_5 = /^(.+?)e$/; + var re_5_1 = /ll$/; + var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var porterStemmer = function porterStemmer(w) { + var stem, + suffix, + firstch, + re, + re2, + re3, + re4; + + if (w.length < 3) { return w; } + + firstch = w.substr(0,1); + if (firstch == "y") { + w = firstch.toUpperCase() + w.substr(1); + } + + // Step 1a + re = re_1a + re2 = re2_1a; + + if (re.test(w)) { w = w.replace(re,"$1$2"); } + else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } + + // Step 1b + re = re_1b; + re2 = re2_1b; + if (re.test(w)) { + var fp = re.exec(w); + re = re_mgr0; + if (re.test(fp[1])) { + re = re_1b_2; + w = w.replace(re,""); + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = re_s_v; + if (re2.test(stem)) { + w = stem; + re2 = re2_1b_2; + re3 = re3_1b_2; + re4 = re4_1b_2; + if (re2.test(w)) { w = w + "e"; } + else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); } + else if (re4.test(w)) { w = w + "e"; } + } + } + + // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say) + re = re_1c; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + w = stem + "i"; + } + + // Step 2 + re = re_2; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step2list[suffix]; + } + } + + // Step 3 + re = re_3; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step3list[suffix]; + } + } + + // Step 4 + re = re_4; + re2 = re2_4; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + if (re.test(stem)) { + w = stem; + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = re_mgr1; + if (re2.test(stem)) { + w = stem; + } + } + + // Step 5 + re = re_5; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + re2 = re_meq1; + re3 = re3_5; + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { + w = stem; + } + } + + re = re_5_1; + re2 = re_mgr1; + if (re.test(w) && re2.test(w)) { + re = re_1b_2; + w = w.replace(re,""); + } + + // and turn initial Y back to y + + if (firstch == "y") { + w = firstch.toLowerCase() + w.substr(1); + } + + return w; + }; + + return function (token) { + return token.update(porterStemmer); + } +})(); + +lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') +/*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.generateStopWordFilter builds a stopWordFilter function from the provided + * list of stop words. + * + * The built in lunr.stopWordFilter is built using this generator and can be used + * to generate custom stopWordFilters for applications or non English languages. + * + * @function + * @param {Array} token The token to pass through the filter + * @returns {lunr.PipelineFunction} + * @see lunr.Pipeline + * @see lunr.stopWordFilter + */ +lunr.generateStopWordFilter = function (stopWords) { + var words = stopWords.reduce(function (memo, stopWord) { + memo[stopWord] = stopWord + return memo + }, {}) + + return function (token) { + if (token && words[token.toString()] !== token.toString()) return token + } +} + +/** + * lunr.stopWordFilter is an English language stop word list filter, any words + * contained in the list will not be passed through the filter. + * + * This is intended to be used in the Pipeline. If the token does not pass the + * filter then undefined will be returned. + * + * @function + * @implements {lunr.PipelineFunction} + * @params {lunr.Token} token - A token to check for being a stop word. + * @returns {lunr.Token} + * @see {@link lunr.Pipeline} + */ +lunr.stopWordFilter = lunr.generateStopWordFilter([ + 'a', + 'able', + 'about', + 'across', + 'after', + 'all', + 'almost', + 'also', + 'am', + 'among', + 'an', + 'and', + 'any', + 'are', + 'as', + 'at', + 'be', + 'because', + 'been', + 'but', + 'by', + 'can', + 'cannot', + 'could', + 'dear', + 'did', + 'do', + 'does', + 'either', + 'else', + 'ever', + 'every', + 'for', + 'from', + 'get', + 'got', + 'had', + 'has', + 'have', + 'he', + 'her', + 'hers', + 'him', + 'his', + 'how', + 'however', + 'i', + 'if', + 'in', + 'into', + 'is', + 'it', + 'its', + 'just', + 'least', + 'let', + 'like', + 'likely', + 'may', + 'me', + 'might', + 'most', + 'must', + 'my', + 'neither', + 'no', + 'nor', + 'not', + 'of', + 'off', + 'often', + 'on', + 'only', + 'or', + 'other', + 'our', + 'own', + 'rather', + 'said', + 'say', + 'says', + 'she', + 'should', + 'since', + 'so', + 'some', + 'than', + 'that', + 'the', + 'their', + 'them', + 'then', + 'there', + 'these', + 'they', + 'this', + 'tis', + 'to', + 'too', + 'twas', + 'us', + 'wants', + 'was', + 'we', + 'were', + 'what', + 'when', + 'where', + 'which', + 'while', + 'who', + 'whom', + 'why', + 'will', + 'with', + 'would', + 'yet', + 'you', + 'your' +]) + +lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') +/*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.trimmer is a pipeline function for trimming non word + * characters from the beginning and end of tokens before they + * enter the index. + * + * This implementation may not work correctly for non latin + * characters and should either be removed or adapted for use + * with languages with non-latin characters. + * + * @static + * @implements {lunr.PipelineFunction} + * @param {lunr.Token} token The token to pass through the filter + * @returns {lunr.Token} + * @see lunr.Pipeline + */ +lunr.trimmer = function (token) { + return token.update(function (s) { + return s.replace(/^\W+/, '').replace(/\W+$/, '') + }) +} + +lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') +/*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A token set is used to store the unique list of all tokens + * within an index. Token sets are also used to represent an + * incoming query to the index, this query token set and index + * token set are then intersected to find which tokens to look + * up in the inverted index. + * + * A token set can hold multiple tokens, as in the case of the + * index token set, or it can hold a single token as in the + * case of a simple query token set. + * + * Additionally token sets are used to perform wildcard matching. + * Leading, contained and trailing wildcards are supported, and + * from this edit distance matching can also be provided. + * + * Token sets are implemented as a minimal finite state automata, + * where both common prefixes and suffixes are shared between tokens. + * This helps to reduce the space used for storing the token set. + * + * @constructor + */ +lunr.TokenSet = function () { + this.final = false + this.edges = {} + this.id = lunr.TokenSet._nextId + lunr.TokenSet._nextId += 1 +} + +/** + * Keeps track of the next, auto increment, identifier to assign + * to a new tokenSet. + * + * TokenSets require a unique identifier to be correctly minimised. + * + * @private + */ +lunr.TokenSet._nextId = 1 + +/** + * Creates a TokenSet instance from the given sorted array of words. + * + * @param {String[]} arr - A sorted array of strings to create the set from. + * @returns {lunr.TokenSet} + * @throws Will throw an error if the input array is not sorted. + */ +lunr.TokenSet.fromArray = function (arr) { + var builder = new lunr.TokenSet.Builder + + for (var i = 0, len = arr.length; i < len; i++) { + builder.insert(arr[i]) + } + + builder.finish() + return builder.root +} + +/** + * Creates a token set from a query clause. + * + * @private + * @param {Object} clause - A single clause from lunr.Query. + * @param {string} clause.term - The query clause term. + * @param {number} [clause.editDistance] - The optional edit distance for the term. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.fromClause = function (clause) { + if ('editDistance' in clause) { + return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance) + } else { + return lunr.TokenSet.fromString(clause.term) + } +} + +/** + * Creates a token set representing a single string with a specified + * edit distance. + * + * Insertions, deletions, substitutions and transpositions are each + * treated as an edit distance of 1. + * + * Increasing the allowed edit distance will have a dramatic impact + * on the performance of both creating and intersecting these TokenSets. + * It is advised to keep the edit distance less than 3. + * + * @param {string} str - The string to create the token set from. + * @param {number} editDistance - The allowed edit distance to match. + * @returns {lunr.Vector} + */ +lunr.TokenSet.fromFuzzyString = function (str, editDistance) { + var root = new lunr.TokenSet + + var stack = [{ + node: root, + editsRemaining: editDistance, + str: str + }] + + while (stack.length) { + var frame = stack.pop() + + // no edit + if (frame.str.length > 0) { + var char = frame.str.charAt(0), + noEditNode + + if (char in frame.node.edges) { + noEditNode = frame.node.edges[char] + } else { + noEditNode = new lunr.TokenSet + frame.node.edges[char] = noEditNode + } + + if (frame.str.length == 1) { + noEditNode.final = true + } + + stack.push({ + node: noEditNode, + editsRemaining: frame.editsRemaining, + str: frame.str.slice(1) + }) + } + + if (frame.editsRemaining == 0) { + continue + } + + // insertion + if ("*" in frame.node.edges) { + var insertionNode = frame.node.edges["*"] + } else { + var insertionNode = new lunr.TokenSet + frame.node.edges["*"] = insertionNode + } + + if (frame.str.length == 0) { + insertionNode.final = true + } + + stack.push({ + node: insertionNode, + editsRemaining: frame.editsRemaining - 1, + str: frame.str + }) + + // deletion + // can only do a deletion if we have enough edits remaining + // and if there are characters left to delete in the string + if (frame.str.length > 1) { + stack.push({ + node: frame.node, + editsRemaining: frame.editsRemaining - 1, + str: frame.str.slice(1) + }) + } + + // deletion + // just removing the last character from the str + if (frame.str.length == 1) { + frame.node.final = true + } + + // substitution + // can only do a substitution if we have enough edits remaining + // and if there are characters left to substitute + if (frame.str.length >= 1) { + if ("*" in frame.node.edges) { + var substitutionNode = frame.node.edges["*"] + } else { + var substitutionNode = new lunr.TokenSet + frame.node.edges["*"] = substitutionNode + } + + if (frame.str.length == 1) { + substitutionNode.final = true + } + + stack.push({ + node: substitutionNode, + editsRemaining: frame.editsRemaining - 1, + str: frame.str.slice(1) + }) + } + + // transposition + // can only do a transposition if there are edits remaining + // and there are enough characters to transpose + if (frame.str.length > 1) { + var charA = frame.str.charAt(0), + charB = frame.str.charAt(1), + transposeNode + + if (charB in frame.node.edges) { + transposeNode = frame.node.edges[charB] + } else { + transposeNode = new lunr.TokenSet + frame.node.edges[charB] = transposeNode + } + + if (frame.str.length == 1) { + transposeNode.final = true + } + + stack.push({ + node: transposeNode, + editsRemaining: frame.editsRemaining - 1, + str: charA + frame.str.slice(2) + }) + } + } + + return root +} + +/** + * Creates a TokenSet from a string. + * + * The string may contain one or more wildcard characters (*) + * that will allow wildcard matching when intersecting with + * another TokenSet. + * + * @param {string} str - The string to create a TokenSet from. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.fromString = function (str) { + var node = new lunr.TokenSet, + root = node + + /* + * Iterates through all characters within the passed string + * appending a node for each character. + * + * When a wildcard character is found then a self + * referencing edge is introduced to continually match + * any number of any characters. + */ + for (var i = 0, len = str.length; i < len; i++) { + var char = str[i], + final = (i == len - 1) + + if (char == "*") { + node.edges[char] = node + node.final = final + + } else { + var next = new lunr.TokenSet + next.final = final + + node.edges[char] = next + node = next + } + } + + return root +} + +/** + * Converts this TokenSet into an array of strings + * contained within the TokenSet. + * + * This is not intended to be used on a TokenSet that + * contains wildcards, in these cases the results are + * undefined and are likely to cause an infinite loop. + * + * @returns {string[]} + */ +lunr.TokenSet.prototype.toArray = function () { + var words = [] + + var stack = [{ + prefix: "", + node: this + }] + + while (stack.length) { + var frame = stack.pop(), + edges = Object.keys(frame.node.edges), + len = edges.length + + if (frame.node.final) { + /* In Safari, at this point the prefix is sometimes corrupted, see: + * https://github.com/olivernn/lunr.js/issues/279 Calling any + * String.prototype method forces Safari to "cast" this string to what + * it's supposed to be, fixing the bug. */ + frame.prefix.charAt(0) + words.push(frame.prefix) + } + + for (var i = 0; i < len; i++) { + var edge = edges[i] + + stack.push({ + prefix: frame.prefix.concat(edge), + node: frame.node.edges[edge] + }) + } + } + + return words +} + +/** + * Generates a string representation of a TokenSet. + * + * This is intended to allow TokenSets to be used as keys + * in objects, largely to aid the construction and minimisation + * of a TokenSet. As such it is not designed to be a human + * friendly representation of the TokenSet. + * + * @returns {string} + */ +lunr.TokenSet.prototype.toString = function () { + // NOTE: Using Object.keys here as this.edges is very likely + // to enter 'hash-mode' with many keys being added + // + // avoiding a for-in loop here as it leads to the function + // being de-optimised (at least in V8). From some simple + // benchmarks the performance is comparable, but allowing + // V8 to optimize may mean easy performance wins in the future. + + if (this._str) { + return this._str + } + + var str = this.final ? '1' : '0', + labels = Object.keys(this.edges).sort(), + len = labels.length + + for (var i = 0; i < len; i++) { + var label = labels[i], + node = this.edges[label] + + str = str + label + node.id + } + + return str +} + +/** + * Returns a new TokenSet that is the intersection of + * this TokenSet and the passed TokenSet. + * + * This intersection will take into account any wildcards + * contained within the TokenSet. + * + * @param {lunr.TokenSet} b - An other TokenSet to intersect with. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.prototype.intersect = function (b) { + var output = new lunr.TokenSet, + frame = undefined + + var stack = [{ + qNode: b, + output: output, + node: this + }] + + while (stack.length) { + frame = stack.pop() + + // NOTE: As with the #toString method, we are using + // Object.keys and a for loop instead of a for-in loop + // as both of these objects enter 'hash' mode, causing + // the function to be de-optimised in V8 + var qEdges = Object.keys(frame.qNode.edges), + qLen = qEdges.length, + nEdges = Object.keys(frame.node.edges), + nLen = nEdges.length + + for (var q = 0; q < qLen; q++) { + var qEdge = qEdges[q] + + for (var n = 0; n < nLen; n++) { + var nEdge = nEdges[n] + + if (nEdge == qEdge || qEdge == '*') { + var node = frame.node.edges[nEdge], + qNode = frame.qNode.edges[qEdge], + final = node.final && qNode.final, + next = undefined + + if (nEdge in frame.output.edges) { + // an edge already exists for this character + // no need to create a new node, just set the finality + // bit unless this node is already final + next = frame.output.edges[nEdge] + next.final = next.final || final + + } else { + // no edge exists yet, must create one + // set the finality bit and insert it + // into the output + next = new lunr.TokenSet + next.final = final + frame.output.edges[nEdge] = next + } + + stack.push({ + qNode: qNode, + output: next, + node: node + }) + } + } + } + } + + return output +} +lunr.TokenSet.Builder = function () { + this.previousWord = "" + this.root = new lunr.TokenSet + this.uncheckedNodes = [] + this.minimizedNodes = {} +} + +lunr.TokenSet.Builder.prototype.insert = function (word) { + var node, + commonPrefix = 0 + + if (word < this.previousWord) { + throw new Error ("Out of order word insertion") + } + + for (var i = 0; i < word.length && i < this.previousWord.length; i++) { + if (word[i] != this.previousWord[i]) break + commonPrefix++ + } + + this.minimize(commonPrefix) + + if (this.uncheckedNodes.length == 0) { + node = this.root + } else { + node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child + } + + for (var i = commonPrefix; i < word.length; i++) { + var nextNode = new lunr.TokenSet, + char = word[i] + + node.edges[char] = nextNode + + this.uncheckedNodes.push({ + parent: node, + char: char, + child: nextNode + }) + + node = nextNode + } + + node.final = true + this.previousWord = word +} + +lunr.TokenSet.Builder.prototype.finish = function () { + this.minimize(0) +} + +lunr.TokenSet.Builder.prototype.minimize = function (downTo) { + for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) { + var node = this.uncheckedNodes[i], + childKey = node.child.toString() + + if (childKey in this.minimizedNodes) { + node.parent.edges[node.char] = this.minimizedNodes[childKey] + } else { + // Cache the key for this node since + // we know it can't change anymore + node.child._str = childKey + + this.minimizedNodes[childKey] = node.child + } + + this.uncheckedNodes.pop() + } +} +/*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * An index contains the built index of all documents and provides a query interface + * to the index. + * + * Usually instances of lunr.Index will not be created using this constructor, instead + * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be + * used to load previously built and serialized indexes. + * + * @constructor + * @param {Object} attrs - The attributes of the built search index. + * @param {Object} attrs.invertedIndex - An index of term/field to document reference. + * @param {Object} attrs.fieldVectors - Field vectors + * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens. + * @param {string[]} attrs.fields - The names of indexed document fields. + * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms. + */ +lunr.Index = function (attrs) { + this.invertedIndex = attrs.invertedIndex + this.fieldVectors = attrs.fieldVectors + this.tokenSet = attrs.tokenSet + this.fields = attrs.fields + this.pipeline = attrs.pipeline +} + +/** + * A result contains details of a document matching a search query. + * @typedef {Object} lunr.Index~Result + * @property {string} ref - The reference of the document this result represents. + * @property {number} score - A number between 0 and 1 representing how similar this document is to the query. + * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match. + */ + +/** + * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple + * query language which itself is parsed into an instance of lunr.Query. + * + * For programmatically building queries it is advised to directly use lunr.Query, the query language + * is best used for human entered text rather than program generated text. + * + * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported + * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello' + * or 'world', though those that contain both will rank higher in the results. + * + * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can + * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding + * wildcards will increase the number of documents that will be found but can also have a negative + * impact on query performance, especially with wildcards at the beginning of a term. + * + * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term + * hello in the title field will match this query. Using a field not present in the index will lead + * to an error being thrown. + * + * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term + * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported + * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2. + * Avoid large values for edit distance to improve query performance. + * + * Each term also supports a presence modifier. By default a term's presence in document is optional, however + * this can be changed to either required or prohibited. For a term's presence to be required in a document the + * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and + * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not + * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'. + * + * To escape special characters the backslash character '\' can be used, this allows searches to include + * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead + * of attempting to apply a boost of 2 to the search term "foo". + * + * @typedef {string} lunr.Index~QueryString + * @example Simple single term query + * hello + * @example Multiple term query + * hello world + * @example term scoped to a field + * title:hello + * @example term with a boost of 10 + * hello^10 + * @example term with an edit distance of 2 + * hello~2 + * @example terms with presence modifiers + * -foo +bar baz + */ + +/** + * Performs a search against the index using lunr query syntax. + * + * Results will be returned sorted by their score, the most relevant results + * will be returned first. For details on how the score is calculated, please see + * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}. + * + * For more programmatic querying use lunr.Index#query. + * + * @param {lunr.Index~QueryString} queryString - A string containing a lunr query. + * @throws {lunr.QueryParseError} If the passed query string cannot be parsed. + * @returns {lunr.Index~Result[]} + */ +lunr.Index.prototype.search = function (queryString) { + return this.query(function (query) { + var parser = new lunr.QueryParser(queryString, query) + parser.parse() + }) +} + +/** + * A query builder callback provides a query object to be used to express + * the query to perform on the index. + * + * @callback lunr.Index~queryBuilder + * @param {lunr.Query} query - The query object to build up. + * @this lunr.Query + */ + +/** + * Performs a query against the index using the yielded lunr.Query object. + * + * If performing programmatic queries against the index, this method is preferred + * over lunr.Index#search so as to avoid the additional query parsing overhead. + * + * A query object is yielded to the supplied function which should be used to + * express the query to be run against the index. + * + * Note that although this function takes a callback parameter it is _not_ an + * asynchronous operation, the callback is just yielded a query object to be + * customized. + * + * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query. + * @returns {lunr.Index~Result[]} + */ +lunr.Index.prototype.query = function (fn) { + // for each query clause + // * process terms + // * expand terms from token set + // * find matching documents and metadata + // * get document vectors + // * score documents + + var query = new lunr.Query(this.fields), + matchingFields = Object.create(null), + queryVectors = Object.create(null), + termFieldCache = Object.create(null), + requiredMatches = Object.create(null), + prohibitedMatches = Object.create(null) + + /* + * To support field level boosts a query vector is created per + * field. An empty vector is eagerly created to support negated + * queries. + */ + for (var i = 0; i < this.fields.length; i++) { + queryVectors[this.fields[i]] = new lunr.Vector + } + + fn.call(query, query) + + for (var i = 0; i < query.clauses.length; i++) { + /* + * Unless the pipeline has been disabled for this term, which is + * the case for terms with wildcards, we need to pass the clause + * term through the search pipeline. A pipeline returns an array + * of processed terms. Pipeline functions may expand the passed + * term, which means we may end up performing multiple index lookups + * for a single query term. + */ + var clause = query.clauses[i], + terms = null, + clauseMatches = lunr.Set.empty + + if (clause.usePipeline) { + terms = this.pipeline.runString(clause.term, { + fields: clause.fields + }) + } else { + terms = [clause.term] + } + + for (var m = 0; m < terms.length; m++) { + var term = terms[m] + + /* + * Each term returned from the pipeline needs to use the same query + * clause object, e.g. the same boost and or edit distance. The + * simplest way to do this is to re-use the clause object but mutate + * its term property. + */ + clause.term = term + + /* + * From the term in the clause we create a token set which will then + * be used to intersect the indexes token set to get a list of terms + * to lookup in the inverted index + */ + var termTokenSet = lunr.TokenSet.fromClause(clause), + expandedTerms = this.tokenSet.intersect(termTokenSet).toArray() + + /* + * If a term marked as required does not exist in the tokenSet it is + * impossible for the search to return any matches. We set all the field + * scoped required matches set to empty and stop examining any further + * clauses. + */ + if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) { + for (var k = 0; k < clause.fields.length; k++) { + var field = clause.fields[k] + requiredMatches[field] = lunr.Set.empty + } + + break + } + + for (var j = 0; j < expandedTerms.length; j++) { + /* + * For each term get the posting and termIndex, this is required for + * building the query vector. + */ + var expandedTerm = expandedTerms[j], + posting = this.invertedIndex[expandedTerm], + termIndex = posting._index + + for (var k = 0; k < clause.fields.length; k++) { + /* + * For each field that this query term is scoped by (by default + * all fields are in scope) we need to get all the document refs + * that have this term in that field. + * + * The posting is the entry in the invertedIndex for the matching + * term from above. + */ + var field = clause.fields[k], + fieldPosting = posting[field], + matchingDocumentRefs = Object.keys(fieldPosting), + termField = expandedTerm + "/" + field, + matchingDocumentsSet = new lunr.Set(matchingDocumentRefs) + + /* + * if the presence of this term is required ensure that the matching + * documents are added to the set of required matches for this clause. + * + */ + if (clause.presence == lunr.Query.presence.REQUIRED) { + clauseMatches = clauseMatches.union(matchingDocumentsSet) + + if (requiredMatches[field] === undefined) { + requiredMatches[field] = lunr.Set.complete + } + } + + /* + * if the presence of this term is prohibited ensure that the matching + * documents are added to the set of prohibited matches for this field, + * creating that set if it does not yet exist. + */ + if (clause.presence == lunr.Query.presence.PROHIBITED) { + if (prohibitedMatches[field] === undefined) { + prohibitedMatches[field] = lunr.Set.empty + } + + prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet) + + /* + * Prohibited matches should not be part of the query vector used for + * similarity scoring and no metadata should be extracted so we continue + * to the next field + */ + continue + } + + /* + * The query field vector is populated using the termIndex found for + * the term and a unit value with the appropriate boost applied. + * Using upsert because there could already be an entry in the vector + * for the term we are working with. In that case we just add the scores + * together. + */ + queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b }) + + /** + * If we've already seen this term, field combo then we've already collected + * the matching documents and metadata, no need to go through all that again + */ + if (termFieldCache[termField]) { + continue + } + + for (var l = 0; l < matchingDocumentRefs.length; l++) { + /* + * All metadata for this term/field/document triple + * are then extracted and collected into an instance + * of lunr.MatchData ready to be returned in the query + * results + */ + var matchingDocumentRef = matchingDocumentRefs[l], + matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field), + metadata = fieldPosting[matchingDocumentRef], + fieldMatch + + if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) { + matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata) + } else { + fieldMatch.add(expandedTerm, field, metadata) + } + + } + + termFieldCache[termField] = true + } + } + } + + /** + * If the presence was required we need to update the requiredMatches field sets. + * We do this after all fields for the term have collected their matches because + * the clause terms presence is required in _any_ of the fields not _all_ of the + * fields. + */ + if (clause.presence === lunr.Query.presence.REQUIRED) { + for (var k = 0; k < clause.fields.length; k++) { + var field = clause.fields[k] + requiredMatches[field] = requiredMatches[field].intersect(clauseMatches) + } + } + } + + /** + * Need to combine the field scoped required and prohibited + * matching documents into a global set of required and prohibited + * matches + */ + var allRequiredMatches = lunr.Set.complete, + allProhibitedMatches = lunr.Set.empty + + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i] + + if (requiredMatches[field]) { + allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field]) + } + + if (prohibitedMatches[field]) { + allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field]) + } + } + + var matchingFieldRefs = Object.keys(matchingFields), + results = [], + matches = Object.create(null) + + /* + * If the query is negated (contains only prohibited terms) + * we need to get _all_ fieldRefs currently existing in the + * index. This is only done when we know that the query is + * entirely prohibited terms to avoid any cost of getting all + * fieldRefs unnecessarily. + * + * Additionally, blank MatchData must be created to correctly + * populate the results. + */ + if (query.isNegated()) { + matchingFieldRefs = Object.keys(this.fieldVectors) + + for (var i = 0; i < matchingFieldRefs.length; i++) { + var matchingFieldRef = matchingFieldRefs[i] + var fieldRef = lunr.FieldRef.fromString(matchingFieldRef) + matchingFields[matchingFieldRef] = new lunr.MatchData + } + } + + for (var i = 0; i < matchingFieldRefs.length; i++) { + /* + * Currently we have document fields that match the query, but we + * need to return documents. The matchData and scores are combined + * from multiple fields belonging to the same document. + * + * Scores are calculated by field, using the query vectors created + * above, and combined into a final document score using addition. + */ + var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]), + docRef = fieldRef.docRef + + if (!allRequiredMatches.contains(docRef)) { + continue + } + + if (allProhibitedMatches.contains(docRef)) { + continue + } + + var fieldVector = this.fieldVectors[fieldRef], + score = queryVectors[fieldRef.fieldName].similarity(fieldVector), + docMatch + + if ((docMatch = matches[docRef]) !== undefined) { + docMatch.score += score + docMatch.matchData.combine(matchingFields[fieldRef]) + } else { + var match = { + ref: docRef, + score: score, + matchData: matchingFields[fieldRef] + } + matches[docRef] = match + results.push(match) + } + } + + /* + * Sort the results objects by score, highest first. + */ + return results.sort(function (a, b) { + return b.score - a.score + }) +} + +/** + * Prepares the index for JSON serialization. + * + * The schema for this JSON blob will be described in a + * separate JSON schema file. + * + * @returns {Object} + */ +lunr.Index.prototype.toJSON = function () { + var invertedIndex = Object.keys(this.invertedIndex) + .sort() + .map(function (term) { + return [term, this.invertedIndex[term]] + }, this) + + var fieldVectors = Object.keys(this.fieldVectors) + .map(function (ref) { + return [ref, this.fieldVectors[ref].toJSON()] + }, this) + + return { + version: lunr.version, + fields: this.fields, + fieldVectors: fieldVectors, + invertedIndex: invertedIndex, + pipeline: this.pipeline.toJSON() + } +} + +/** + * Loads a previously serialized lunr.Index + * + * @param {Object} serializedIndex - A previously serialized lunr.Index + * @returns {lunr.Index} + */ +lunr.Index.load = function (serializedIndex) { + var attrs = {}, + fieldVectors = {}, + serializedVectors = serializedIndex.fieldVectors, + invertedIndex = Object.create(null), + serializedInvertedIndex = serializedIndex.invertedIndex, + tokenSetBuilder = new lunr.TokenSet.Builder, + pipeline = lunr.Pipeline.load(serializedIndex.pipeline) + + if (serializedIndex.version != lunr.version) { + lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'") + } + + for (var i = 0; i < serializedVectors.length; i++) { + var tuple = serializedVectors[i], + ref = tuple[0], + elements = tuple[1] + + fieldVectors[ref] = new lunr.Vector(elements) + } + + for (var i = 0; i < serializedInvertedIndex.length; i++) { + var tuple = serializedInvertedIndex[i], + term = tuple[0], + posting = tuple[1] + + tokenSetBuilder.insert(term) + invertedIndex[term] = posting + } + + tokenSetBuilder.finish() + + attrs.fields = serializedIndex.fields + + attrs.fieldVectors = fieldVectors + attrs.invertedIndex = invertedIndex + attrs.tokenSet = tokenSetBuilder.root + attrs.pipeline = pipeline + + return new lunr.Index(attrs) +} +/*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.Builder performs indexing on a set of documents and + * returns instances of lunr.Index ready for querying. + * + * All configuration of the index is done via the builder, the + * fields to index, the document reference, the text processing + * pipeline and document scoring parameters are all set on the + * builder before indexing. + * + * @constructor + * @property {string} _ref - Internal reference to the document reference field. + * @property {string[]} _fields - Internal reference to the document fields to index. + * @property {object} invertedIndex - The inverted index maps terms to document fields. + * @property {object} documentTermFrequencies - Keeps track of document term frequencies. + * @property {object} documentLengths - Keeps track of the length of documents added to the index. + * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing. + * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing. + * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index. + * @property {number} documentCount - Keeps track of the total number of documents indexed. + * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75. + * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2. + * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space. + * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index. + */ +lunr.Builder = function () { + this._ref = "id" + this._fields = Object.create(null) + this._documents = Object.create(null) + this.invertedIndex = Object.create(null) + this.fieldTermFrequencies = {} + this.fieldLengths = {} + this.tokenizer = lunr.tokenizer + this.pipeline = new lunr.Pipeline + this.searchPipeline = new lunr.Pipeline + this.documentCount = 0 + this._b = 0.75 + this._k1 = 1.2 + this.termIndex = 0 + this.metadataWhitelist = [] +} + +/** + * Sets the document field used as the document reference. Every document must have this field. + * The type of this field in the document should be a string, if it is not a string it will be + * coerced into a string by calling toString. + * + * The default ref is 'id'. + * + * The ref should _not_ be changed during indexing, it should be set before any documents are + * added to the index. Changing it during indexing can lead to inconsistent results. + * + * @param {string} ref - The name of the reference field in the document. + */ +lunr.Builder.prototype.ref = function (ref) { + this._ref = ref +} + +/** + * A function that is used to extract a field from a document. + * + * Lunr expects a field to be at the top level of a document, if however the field + * is deeply nested within a document an extractor function can be used to extract + * the right field for indexing. + * + * @callback fieldExtractor + * @param {object} doc - The document being added to the index. + * @returns {?(string|object|object[])} obj - The object that will be indexed for this field. + * @example Extracting a nested field + * function (doc) { return doc.nested.field } + */ + +/** + * Adds a field to the list of document fields that will be indexed. Every document being + * indexed should have this field. Null values for this field in indexed documents will + * not cause errors but will limit the chance of that document being retrieved by searches. + * + * All fields should be added before adding documents to the index. Adding fields after + * a document has been indexed will have no effect on already indexed documents. + * + * Fields can be boosted at build time. This allows terms within that field to have more + * importance when ranking search results. Use a field boost to specify that matches within + * one field are more important than other fields. + * + * @param {string} fieldName - The name of a field to index in all documents. + * @param {object} attributes - Optional attributes associated with this field. + * @param {number} [attributes.boost=1] - Boost applied to all terms within this field. + * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document. + * @throws {RangeError} fieldName cannot contain unsupported characters '/' + */ +lunr.Builder.prototype.field = function (fieldName, attributes) { + if (/\//.test(fieldName)) { + throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'") + } + + this._fields[fieldName] = attributes || {} +} + +/** + * A parameter to tune the amount of field length normalisation that is applied when + * calculating relevance scores. A value of 0 will completely disable any normalisation + * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b + * will be clamped to the range 0 - 1. + * + * @param {number} number - The value to set for this tuning parameter. + */ +lunr.Builder.prototype.b = function (number) { + if (number < 0) { + this._b = 0 + } else if (number > 1) { + this._b = 1 + } else { + this._b = number + } +} + +/** + * A parameter that controls the speed at which a rise in term frequency results in term + * frequency saturation. The default value is 1.2. Setting this to a higher value will give + * slower saturation levels, a lower value will result in quicker saturation. + * + * @param {number} number - The value to set for this tuning parameter. + */ +lunr.Builder.prototype.k1 = function (number) { + this._k1 = number +} + +/** + * Adds a document to the index. + * + * Before adding fields to the index the index should have been fully setup, with the document + * ref and all fields to index already having been specified. + * + * The document must have a field name as specified by the ref (by default this is 'id') and + * it should have all fields defined for indexing, though null or undefined values will not + * cause errors. + * + * Entire documents can be boosted at build time. Applying a boost to a document indicates that + * this document should rank higher in search results than other documents. + * + * @param {object} doc - The document to add to the index. + * @param {object} attributes - Optional attributes associated with this document. + * @param {number} [attributes.boost=1] - Boost applied to all terms within this document. + */ +lunr.Builder.prototype.add = function (doc, attributes) { + var docRef = doc[this._ref], + fields = Object.keys(this._fields) + + this._documents[docRef] = attributes || {} + this.documentCount += 1 + + for (var i = 0; i < fields.length; i++) { + var fieldName = fields[i], + extractor = this._fields[fieldName].extractor, + field = extractor ? extractor(doc) : doc[fieldName], + tokens = this.tokenizer(field, { + fields: [fieldName] + }), + terms = this.pipeline.run(tokens), + fieldRef = new lunr.FieldRef (docRef, fieldName), + fieldTerms = Object.create(null) + + this.fieldTermFrequencies[fieldRef] = fieldTerms + this.fieldLengths[fieldRef] = 0 + + // store the length of this field for this document + this.fieldLengths[fieldRef] += terms.length + + // calculate term frequencies for this field + for (var j = 0; j < terms.length; j++) { + var term = terms[j] + + if (fieldTerms[term] == undefined) { + fieldTerms[term] = 0 + } + + fieldTerms[term] += 1 + + // add to inverted index + // create an initial posting if one doesn't exist + if (this.invertedIndex[term] == undefined) { + var posting = Object.create(null) + posting["_index"] = this.termIndex + this.termIndex += 1 + + for (var k = 0; k < fields.length; k++) { + posting[fields[k]] = Object.create(null) + } + + this.invertedIndex[term] = posting + } + + // add an entry for this term/fieldName/docRef to the invertedIndex + if (this.invertedIndex[term][fieldName][docRef] == undefined) { + this.invertedIndex[term][fieldName][docRef] = Object.create(null) + } + + // store all whitelisted metadata about this token in the + // inverted index + for (var l = 0; l < this.metadataWhitelist.length; l++) { + var metadataKey = this.metadataWhitelist[l], + metadata = term.metadata[metadataKey] + + if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) { + this.invertedIndex[term][fieldName][docRef][metadataKey] = [] + } + + this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata) + } + } + + } +} + +/** + * Calculates the average document length for this index + * + * @private + */ +lunr.Builder.prototype.calculateAverageFieldLengths = function () { + + var fieldRefs = Object.keys(this.fieldLengths), + numberOfFields = fieldRefs.length, + accumulator = {}, + documentsWithField = {} + + for (var i = 0; i < numberOfFields; i++) { + var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), + field = fieldRef.fieldName + + documentsWithField[field] || (documentsWithField[field] = 0) + documentsWithField[field] += 1 + + accumulator[field] || (accumulator[field] = 0) + accumulator[field] += this.fieldLengths[fieldRef] + } + + var fields = Object.keys(this._fields) + + for (var i = 0; i < fields.length; i++) { + var fieldName = fields[i] + accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName] + } + + this.averageFieldLength = accumulator +} + +/** + * Builds a vector space model of every document using lunr.Vector + * + * @private + */ +lunr.Builder.prototype.createFieldVectors = function () { + var fieldVectors = {}, + fieldRefs = Object.keys(this.fieldTermFrequencies), + fieldRefsLength = fieldRefs.length, + termIdfCache = Object.create(null) + + for (var i = 0; i < fieldRefsLength; i++) { + var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), + fieldName = fieldRef.fieldName, + fieldLength = this.fieldLengths[fieldRef], + fieldVector = new lunr.Vector, + termFrequencies = this.fieldTermFrequencies[fieldRef], + terms = Object.keys(termFrequencies), + termsLength = terms.length + + + var fieldBoost = this._fields[fieldName].boost || 1, + docBoost = this._documents[fieldRef.docRef].boost || 1 + + for (var j = 0; j < termsLength; j++) { + var term = terms[j], + tf = termFrequencies[term], + termIndex = this.invertedIndex[term]._index, + idf, score, scoreWithPrecision + + if (termIdfCache[term] === undefined) { + idf = lunr.idf(this.invertedIndex[term], this.documentCount) + termIdfCache[term] = idf + } else { + idf = termIdfCache[term] + } + + score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf) + score *= fieldBoost + score *= docBoost + scoreWithPrecision = Math.round(score * 1000) / 1000 + // Converts 1.23456789 to 1.234. + // Reducing the precision so that the vectors take up less + // space when serialised. Doing it now so that they behave + // the same before and after serialisation. Also, this is + // the fastest approach to reducing a number's precision in + // JavaScript. + + fieldVector.insert(termIndex, scoreWithPrecision) + } + + fieldVectors[fieldRef] = fieldVector + } + + this.fieldVectors = fieldVectors +} + +/** + * Creates a token set of all tokens in the index using lunr.TokenSet + * + * @private + */ +lunr.Builder.prototype.createTokenSet = function () { + this.tokenSet = lunr.TokenSet.fromArray( + Object.keys(this.invertedIndex).sort() + ) +} + +/** + * Builds the index, creating an instance of lunr.Index. + * + * This completes the indexing process and should only be called + * once all documents have been added to the index. + * + * @returns {lunr.Index} + */ +lunr.Builder.prototype.build = function () { + this.calculateAverageFieldLengths() + this.createFieldVectors() + this.createTokenSet() + + return new lunr.Index({ + invertedIndex: this.invertedIndex, + fieldVectors: this.fieldVectors, + tokenSet: this.tokenSet, + fields: Object.keys(this._fields), + pipeline: this.searchPipeline + }) +} + +/** + * Applies a plugin to the index builder. + * + * A plugin is a function that is called with the index builder as its context. + * Plugins can be used to customise or extend the behaviour of the index + * in some way. A plugin is just a function, that encapsulated the custom + * behaviour that should be applied when building the index. + * + * The plugin function will be called with the index builder as its argument, additional + * arguments can also be passed when calling use. The function will be called + * with the index builder as its context. + * + * @param {Function} plugin The plugin to apply. + */ +lunr.Builder.prototype.use = function (fn) { + var args = Array.prototype.slice.call(arguments, 1) + args.unshift(this) + fn.apply(this, args) +} +/** + * Contains and collects metadata about a matching document. + * A single instance of lunr.MatchData is returned as part of every + * lunr.Index~Result. + * + * @constructor + * @param {string} term - The term this match data is associated with + * @param {string} field - The field in which the term was found + * @param {object} metadata - The metadata recorded about this term in this field + * @property {object} metadata - A cloned collection of metadata associated with this document. + * @see {@link lunr.Index~Result} + */ +lunr.MatchData = function (term, field, metadata) { + var clonedMetadata = Object.create(null), + metadataKeys = Object.keys(metadata || {}) + + // Cloning the metadata to prevent the original + // being mutated during match data combination. + // Metadata is kept in an array within the inverted + // index so cloning the data can be done with + // Array#slice + for (var i = 0; i < metadataKeys.length; i++) { + var key = metadataKeys[i] + clonedMetadata[key] = metadata[key].slice() + } + + this.metadata = Object.create(null) + + if (term !== undefined) { + this.metadata[term] = Object.create(null) + this.metadata[term][field] = clonedMetadata + } +} + +/** + * An instance of lunr.MatchData will be created for every term that matches a + * document. However only one instance is required in a lunr.Index~Result. This + * method combines metadata from another instance of lunr.MatchData with this + * objects metadata. + * + * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one. + * @see {@link lunr.Index~Result} + */ +lunr.MatchData.prototype.combine = function (otherMatchData) { + var terms = Object.keys(otherMatchData.metadata) + + for (var i = 0; i < terms.length; i++) { + var term = terms[i], + fields = Object.keys(otherMatchData.metadata[term]) + + if (this.metadata[term] == undefined) { + this.metadata[term] = Object.create(null) + } + + for (var j = 0; j < fields.length; j++) { + var field = fields[j], + keys = Object.keys(otherMatchData.metadata[term][field]) + + if (this.metadata[term][field] == undefined) { + this.metadata[term][field] = Object.create(null) + } + + for (var k = 0; k < keys.length; k++) { + var key = keys[k] + + if (this.metadata[term][field][key] == undefined) { + this.metadata[term][field][key] = otherMatchData.metadata[term][field][key] + } else { + this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key]) + } + + } + } + } +} + +/** + * Add metadata for a term/field pair to this instance of match data. + * + * @param {string} term - The term this match data is associated with + * @param {string} field - The field in which the term was found + * @param {object} metadata - The metadata recorded about this term in this field + */ +lunr.MatchData.prototype.add = function (term, field, metadata) { + if (!(term in this.metadata)) { + this.metadata[term] = Object.create(null) + this.metadata[term][field] = metadata + return + } + + if (!(field in this.metadata[term])) { + this.metadata[term][field] = metadata + return + } + + var metadataKeys = Object.keys(metadata) + + for (var i = 0; i < metadataKeys.length; i++) { + var key = metadataKeys[i] + + if (key in this.metadata[term][field]) { + this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key]) + } else { + this.metadata[term][field][key] = metadata[key] + } + } +} +/** + * A lunr.Query provides a programmatic way of defining queries to be performed + * against a {@link lunr.Index}. + * + * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method + * so the query object is pre-initialized with the right index fields. + * + * @constructor + * @property {lunr.Query~Clause[]} clauses - An array of query clauses. + * @property {string[]} allFields - An array of all available fields in a lunr.Index. + */ +lunr.Query = function (allFields) { + this.clauses = [] + this.allFields = allFields +} + +/** + * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause. + * + * This allows wildcards to be added to the beginning and end of a term without having to manually do any string + * concatenation. + * + * The wildcard constants can be bitwise combined to select both leading and trailing wildcards. + * + * @constant + * @default + * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour + * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists + * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists + * @see lunr.Query~Clause + * @see lunr.Query#clause + * @see lunr.Query#term + * @example query term with trailing wildcard + * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING }) + * @example query term with leading and trailing wildcard + * query.term('foo', { + * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING + * }) + */ + +lunr.Query.wildcard = new String ("*") +lunr.Query.wildcard.NONE = 0 +lunr.Query.wildcard.LEADING = 1 +lunr.Query.wildcard.TRAILING = 2 + +/** + * Constants for indicating what kind of presence a term must have in matching documents. + * + * @constant + * @enum {number} + * @see lunr.Query~Clause + * @see lunr.Query#clause + * @see lunr.Query#term + * @example query term with required presence + * query.term('foo', { presence: lunr.Query.presence.REQUIRED }) + */ +lunr.Query.presence = { + /** + * Term's presence in a document is optional, this is the default value. + */ + OPTIONAL: 1, + + /** + * Term's presence in a document is required, documents that do not contain + * this term will not be returned. + */ + REQUIRED: 2, + + /** + * Term's presence in a document is prohibited, documents that do contain + * this term will not be returned. + */ + PROHIBITED: 3 +} + +/** + * A single clause in a {@link lunr.Query} contains a term and details on how to + * match that term against a {@link lunr.Index}. + * + * @typedef {Object} lunr.Query~Clause + * @property {string[]} fields - The fields in an index this clause should be matched against. + * @property {number} [boost=1] - Any boost that should be applied when matching this clause. + * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be. + * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline. + * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended. + * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents. + */ + +/** + * Adds a {@link lunr.Query~Clause} to this query. + * + * Unless the clause contains the fields to be matched all fields will be matched. In addition + * a default boost of 1 is applied to the clause. + * + * @param {lunr.Query~Clause} clause - The clause to add to this query. + * @see lunr.Query~Clause + * @returns {lunr.Query} + */ +lunr.Query.prototype.clause = function (clause) { + if (!('fields' in clause)) { + clause.fields = this.allFields + } + + if (!('boost' in clause)) { + clause.boost = 1 + } + + if (!('usePipeline' in clause)) { + clause.usePipeline = true + } + + if (!('wildcard' in clause)) { + clause.wildcard = lunr.Query.wildcard.NONE + } + + if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) { + clause.term = "*" + clause.term + } + + if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) { + clause.term = "" + clause.term + "*" + } + + if (!('presence' in clause)) { + clause.presence = lunr.Query.presence.OPTIONAL + } + + this.clauses.push(clause) + + return this +} + +/** + * A negated query is one in which every clause has a presence of + * prohibited. These queries require some special processing to return + * the expected results. + * + * @returns boolean + */ +lunr.Query.prototype.isNegated = function () { + for (var i = 0; i < this.clauses.length; i++) { + if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) { + return false + } + } + + return true +} + +/** + * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause} + * to the list of clauses that make up this query. + * + * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion + * to a token or token-like string should be done before calling this method. + * + * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an + * array, each term in the array will share the same options. + * + * @param {object|object[]} term - The term(s) to add to the query. + * @param {object} [options] - Any additional properties to add to the query clause. + * @returns {lunr.Query} + * @see lunr.Query#clause + * @see lunr.Query~Clause + * @example adding a single term to a query + * query.term("foo") + * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard + * query.term("foo", { + * fields: ["title"], + * boost: 10, + * wildcard: lunr.Query.wildcard.TRAILING + * }) + * @example using lunr.tokenizer to convert a string to tokens before using them as terms + * query.term(lunr.tokenizer("foo bar")) + */ +lunr.Query.prototype.term = function (term, options) { + if (Array.isArray(term)) { + term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this) + return this + } + + var clause = options || {} + clause.term = term.toString() + + this.clause(clause) + + return this +} +lunr.QueryParseError = function (message, start, end) { + this.name = "QueryParseError" + this.message = message + this.start = start + this.end = end +} + +lunr.QueryParseError.prototype = new Error +lunr.QueryLexer = function (str) { + this.lexemes = [] + this.str = str + this.length = str.length + this.pos = 0 + this.start = 0 + this.escapeCharPositions = [] +} + +lunr.QueryLexer.prototype.run = function () { + var state = lunr.QueryLexer.lexText + + while (state) { + state = state(this) + } +} + +lunr.QueryLexer.prototype.sliceString = function () { + var subSlices = [], + sliceStart = this.start, + sliceEnd = this.pos + + for (var i = 0; i < this.escapeCharPositions.length; i++) { + sliceEnd = this.escapeCharPositions[i] + subSlices.push(this.str.slice(sliceStart, sliceEnd)) + sliceStart = sliceEnd + 1 + } + + subSlices.push(this.str.slice(sliceStart, this.pos)) + this.escapeCharPositions.length = 0 + + return subSlices.join('') +} + +lunr.QueryLexer.prototype.emit = function (type) { + this.lexemes.push({ + type: type, + str: this.sliceString(), + start: this.start, + end: this.pos + }) + + this.start = this.pos +} + +lunr.QueryLexer.prototype.escapeCharacter = function () { + this.escapeCharPositions.push(this.pos - 1) + this.pos += 1 +} + +lunr.QueryLexer.prototype.next = function () { + if (this.pos >= this.length) { + return lunr.QueryLexer.EOS + } + + var char = this.str.charAt(this.pos) + this.pos += 1 + return char +} + +lunr.QueryLexer.prototype.width = function () { + return this.pos - this.start +} + +lunr.QueryLexer.prototype.ignore = function () { + if (this.start == this.pos) { + this.pos += 1 + } + + this.start = this.pos +} + +lunr.QueryLexer.prototype.backup = function () { + this.pos -= 1 +} + +lunr.QueryLexer.prototype.acceptDigitRun = function () { + var char, charCode + + do { + char = this.next() + charCode = char.charCodeAt(0) + } while (charCode > 47 && charCode < 58) + + if (char != lunr.QueryLexer.EOS) { + this.backup() + } +} + +lunr.QueryLexer.prototype.more = function () { + return this.pos < this.length +} + +lunr.QueryLexer.EOS = 'EOS' +lunr.QueryLexer.FIELD = 'FIELD' +lunr.QueryLexer.TERM = 'TERM' +lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE' +lunr.QueryLexer.BOOST = 'BOOST' +lunr.QueryLexer.PRESENCE = 'PRESENCE' + +lunr.QueryLexer.lexField = function (lexer) { + lexer.backup() + lexer.emit(lunr.QueryLexer.FIELD) + lexer.ignore() + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexTerm = function (lexer) { + if (lexer.width() > 1) { + lexer.backup() + lexer.emit(lunr.QueryLexer.TERM) + } + + lexer.ignore() + + if (lexer.more()) { + return lunr.QueryLexer.lexText + } +} + +lunr.QueryLexer.lexEditDistance = function (lexer) { + lexer.ignore() + lexer.acceptDigitRun() + lexer.emit(lunr.QueryLexer.EDIT_DISTANCE) + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexBoost = function (lexer) { + lexer.ignore() + lexer.acceptDigitRun() + lexer.emit(lunr.QueryLexer.BOOST) + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexEOS = function (lexer) { + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } +} + +// This matches the separator used when tokenising fields +// within a document. These should match otherwise it is +// not possible to search for some tokens within a document. +// +// It is possible for the user to change the separator on the +// tokenizer so it _might_ clash with any other of the special +// characters already used within the search string, e.g. :. +// +// This means that it is possible to change the separator in +// such a way that makes some words unsearchable using a search +// string. +lunr.QueryLexer.termSeparator = lunr.tokenizer.separator + +lunr.QueryLexer.lexText = function (lexer) { + while (true) { + var char = lexer.next() + + if (char == lunr.QueryLexer.EOS) { + return lunr.QueryLexer.lexEOS + } + + // Escape character is '\' + if (char.charCodeAt(0) == 92) { + lexer.escapeCharacter() + continue + } + + if (char == ":") { + return lunr.QueryLexer.lexField + } + + if (char == "~") { + lexer.backup() + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } + return lunr.QueryLexer.lexEditDistance + } + + if (char == "^") { + lexer.backup() + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } + return lunr.QueryLexer.lexBoost + } + + // "+" indicates term presence is required + // checking for length to ensure that only + // leading "+" are considered + if (char == "+" && lexer.width() === 1) { + lexer.emit(lunr.QueryLexer.PRESENCE) + return lunr.QueryLexer.lexText + } + + // "-" indicates term presence is prohibited + // checking for length to ensure that only + // leading "-" are considered + if (char == "-" && lexer.width() === 1) { + lexer.emit(lunr.QueryLexer.PRESENCE) + return lunr.QueryLexer.lexText + } + + if (char.match(lunr.QueryLexer.termSeparator)) { + return lunr.QueryLexer.lexTerm + } + } +} + +lunr.QueryParser = function (str, query) { + this.lexer = new lunr.QueryLexer (str) + this.query = query + this.currentClause = {} + this.lexemeIdx = 0 +} + +lunr.QueryParser.prototype.parse = function () { + this.lexer.run() + this.lexemes = this.lexer.lexemes + + var state = lunr.QueryParser.parseClause + + while (state) { + state = state(this) + } + + return this.query +} + +lunr.QueryParser.prototype.peekLexeme = function () { + return this.lexemes[this.lexemeIdx] +} + +lunr.QueryParser.prototype.consumeLexeme = function () { + var lexeme = this.peekLexeme() + this.lexemeIdx += 1 + return lexeme +} + +lunr.QueryParser.prototype.nextClause = function () { + var completedClause = this.currentClause + this.query.clause(completedClause) + this.currentClause = {} +} + +lunr.QueryParser.parseClause = function (parser) { + var lexeme = parser.peekLexeme() + + if (lexeme == undefined) { + return + } + + switch (lexeme.type) { + case lunr.QueryLexer.PRESENCE: + return lunr.QueryParser.parsePresence + case lunr.QueryLexer.FIELD: + return lunr.QueryParser.parseField + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expected either a field or a term, found " + lexeme.type + + if (lexeme.str.length >= 1) { + errorMessage += " with value '" + lexeme.str + "'" + } + + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } +} + +lunr.QueryParser.parsePresence = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + switch (lexeme.str) { + case "-": + parser.currentClause.presence = lunr.Query.presence.PROHIBITED + break + case "+": + parser.currentClause.presence = lunr.Query.presence.REQUIRED + break + default: + var errorMessage = "unrecognised presence operator'" + lexeme.str + "'" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + var errorMessage = "expecting term or field, found nothing" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.FIELD: + return lunr.QueryParser.parseField + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseField = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + if (parser.query.allFields.indexOf(lexeme.str) == -1) { + var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '), + errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields + + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.fields = [lexeme.str] + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + var errorMessage = "expecting term, found nothing" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expecting term, found '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseTerm = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + parser.currentClause.term = lexeme.str.toLowerCase() + + if (lexeme.str.indexOf("*") != -1) { + parser.currentClause.usePipeline = false + } + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseEditDistance = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + var editDistance = parseInt(lexeme.str, 10) + + if (isNaN(editDistance)) { + var errorMessage = "edit distance must be numeric" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.editDistance = editDistance + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseBoost = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + var boost = parseInt(lexeme.str, 10) + + if (isNaN(boost)) { + var errorMessage = "boost must be numeric" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.boost = boost + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + + /** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ + ;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like environments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + root.lunr = factory() + } + }(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + return lunr + })) +})(); diff --git a/search/main.js b/search/main.js new file mode 100644 index 000000000..a5e469d7c --- /dev/null +++ b/search/main.js @@ -0,0 +1,109 @@ +function getSearchTermFromLocation() { + var sPageURL = window.location.search.substring(1); + var sURLVariables = sPageURL.split('&'); + for (var i = 0; i < sURLVariables.length; i++) { + var sParameterName = sURLVariables[i].split('='); + if (sParameterName[0] == 'q') { + return decodeURIComponent(sParameterName[1].replace(/\+/g, '%20')); + } + } +} + +function joinUrl (base, path) { + if (path.substring(0, 1) === "/") { + // path starts with `/`. Thus it is absolute. + return path; + } + if (base.substring(base.length-1) === "/") { + // base ends with `/` + return base + path; + } + return base + "/" + path; +} + +function escapeHtml (value) { + return value.replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/
/g, '>'); +} + +function formatResult (location, title, summary) { + return ''; +} + +function displayResults (results) { + var search_results = document.getElementById("mkdocs-search-results"); + while (search_results.firstChild) { + search_results.removeChild(search_results.firstChild); + } + if (results.length > 0){ + for (var i=0; i < results.length; i++){ + var result = results[i]; + var html = formatResult(result.location, result.title, result.summary); + search_results.insertAdjacentHTML('beforeend', html); + } + } else { + var noResultsText = search_results.getAttribute('data-no-results-text'); + if (!noResultsText) { + noResultsText = "No results found"; + } + search_results.insertAdjacentHTML('beforeend', '

' + noResultsText + '

'); + } +} + +function doSearch () { + var query = document.getElementById('mkdocs-search-query').value; + if (query.length > min_search_length) { + if (!window.Worker) { + displayResults(search(query)); + } else { + searchWorker.postMessage({query: query}); + } + } else { + // Clear results for short queries + displayResults([]); + } +} + +function initSearch () { + var search_input = document.getElementById('mkdocs-search-query'); + if (search_input) { + search_input.addEventListener("keyup", doSearch); + } + var term = getSearchTermFromLocation(); + if (term) { + search_input.value = term; + doSearch(); + } +} + +function onWorkerMessage (e) { + if (e.data.allowSearch) { + initSearch(); + } else if (e.data.results) { + var results = e.data.results; + displayResults(results); + } else if (e.data.config) { + min_search_length = e.data.config.min_search_length-1; + } +} + +if (!window.Worker) { + console.log('Web Worker API not supported'); + // load index in main thread + $.getScript(joinUrl(base_url, "search/worker.js")).done(function () { + console.log('Loaded worker'); + init(); + window.postMessage = function (msg) { + onWorkerMessage({data: msg}); + }; + }).fail(function (jqxhr, settings, exception) { + console.error('Could not load worker.js'); + }); +} else { + // Wrap search in a web worker + var searchWorker = new Worker(joinUrl(base_url, "search/worker.js")); + searchWorker.postMessage({init: true}); + searchWorker.onmessage = onWorkerMessage; +} diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 000000000..399d67451 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"The Cell Ontology \u00b6 The Cell Ontology (CL) is an OBO Foundry ontology covering the domain of canonical, natural biological cell types. OLS \u00b6 The recommended way to browse the ontology is through the ontology lookup service (OLS) . Accessing CL \u00b6 The main release files can be found in our GitHub repository . The main release file can also be found here . Contributing \u00b6 To see how you can contribute to the cell ontology, please visit our Contributing page . Documentation \u00b6 The documentations here is intended for maintainers and editors of the cell ontology. For general guidance on editing OBO-(ish) ontologies, please see the obook . Cell Ontology Website \u00b6 You can also access our front facing website at https://cell-ontology.github.io/","title":"Getting started"},{"location":"#the-cell-ontology","text":"The Cell Ontology (CL) is an OBO Foundry ontology covering the domain of canonical, natural biological cell types.","title":"The Cell Ontology"},{"location":"#ols","text":"The recommended way to browse the ontology is through the ontology lookup service (OLS) .","title":"OLS"},{"location":"#accessing-cl","text":"The main release files can be found in our GitHub repository . The main release file can also be found here .","title":"Accessing CL"},{"location":"#contributing","text":"To see how you can contribute to the cell ontology, please visit our Contributing page .","title":"Contributing"},{"location":"#documentation","text":"The documentations here is intended for maintainers and editors of the cell ontology. For general guidance on editing OBO-(ish) ontologies, please see the obook .","title":"Documentation"},{"location":"#cell-ontology-website","text":"You can also access our front facing website at https://cell-ontology.github.io/","title":"Cell Ontology Website"},{"location":"Adding_classes_from_another_ontology/","text":"How to add (import) classes to the Cell Ontology (CL) from another ontology \u00b6 NOTE To add PRO terms, please follow first the instructions to add the terms into pro obo slim . 1. Follow steps 1 - 5 under the heading Protege-based declaration. \u00b6 NB: Even though the instructions state that this workflow is to be avoided, the other solutions in the current documentation are out of date. 2. Refresh the imports \u00b6 To refresh the imports, open Docker so it is running in the background. Then open Terminal, navigate to src/ontology directory in the cell-ontology repository and run: sh run.sh make imports/merged_import.owl Running the above command requires > 8GB RAM and sufficient computational power. If the refresh fails to complete due to hardware limitations, create a new issue in GitHub detailing which class(es) need to be imported and another editor can add it on your behalf. Once the imports are refreshed, return to Prot\u00e9g\u00e9, add the logical axioms that include the newly imported class(es) and create a pull request per standard procedure. Note that the import refresh process seems to be quite laborious/computationally expensive as-is, and a centralised database approach may be an improved longterm solution.","title":"Importing classes from another ontology"},{"location":"Adding_classes_from_another_ontology/#how-to-add-import-classes-to-the-cell-ontology-cl-from-another-ontology","text":"NOTE To add PRO terms, please follow first the instructions to add the terms into pro obo slim .","title":"How to add (import) classes to the Cell Ontology (CL) from another ontology"},{"location":"Adding_classes_from_another_ontology/#1-follow-steps-1-5-under-the-heading-protege-based-declaration","text":"NB: Even though the instructions state that this workflow is to be avoided, the other solutions in the current documentation are out of date.","title":"1. Follow steps 1 - 5 under the heading Protege-based declaration."},{"location":"Adding_classes_from_another_ontology/#2-refresh-the-imports","text":"To refresh the imports, open Docker so it is running in the background. Then open Terminal, navigate to src/ontology directory in the cell-ontology repository and run: sh run.sh make imports/merged_import.owl Running the above command requires > 8GB RAM and sufficient computational power. If the refresh fails to complete due to hardware limitations, create a new issue in GitHub detailing which class(es) need to be imported and another editor can add it on your behalf. Once the imports are refreshed, return to Prot\u00e9g\u00e9, add the logical axioms that include the newly imported class(es) and create a pull request per standard procedure. Note that the import refresh process seems to be quite laborious/computationally expensive as-is, and a centralised database approach may be an improved longterm solution.","title":"2. Refresh the imports"},{"location":"Create_upper_level_slim/","text":"SOP for adding a new slim: \u00b6 Intro \u00b6 An upper slim is a set of terms for summarising annotations. CL has both a general slim and domain-specific slims allowing for the generation of general summaries or for domain specific ones. All slims have a context that covers all cells in the domain of interest. In order to fullfil the summarising use-case, a slim should have good % coverage of the domain and, if possible, avoid excluding major cell types. The following are potentially a problem for the grouping use-case: Classes in the slim with very small numbers of subclasses: Having 0-1 subclass => no capacity to summarise. Classes with disproportionately large numbers of subclasses. Overlapping classes - these clash with some types of summary - e.g. pie charts. Potential clashing concerns: It seems reasonable to want to make sure that very important cell types are covered and are not obscured in generating summaries, but this desire can clash with the considerations above. Some judgement is needed to balance these concerns. There is no perfect solution, but some solutions are better (at fulfilling the use case) than others. Name and definition \u00b6 It is important that the name of the slim accurately reflects the content it covers. Specifically, if the intention is to cover all cell types that are specific for an organ, the slim's name should be in the format of \"organ_upper_slim\". This ensures clarity and helps users understand the scope of the slim. Furthermore, the description of the slim should follow a consistent pattern. It is recommended to use the following structure: \"a subset of general classes related to specific cell types in the [organ or specific context]\". This format provides a concise and informative description of the slim, helping users identify its purpose and content. What files to create and edit \u00b6 1. Preparing the subset: \u00b6 Create XXX_upper_slim in Protege (change \"XXX\" to the subset label). See Adding a new Subset . Create a CSV table with the following characteristics (Find examples in src/templates ): 3 columns ID subset label ID AI oboInOwl:inSubset CL:####### http://purl.obolibrary.org/obo/cl#XXX_upper_slim CL term ... http://purl.obolibrary.org/obo/cl#XXX_upper_slim ... Save the CSV file with the name 'XXX_upper_slim.csv' in the src/templates directory. Modify src/ontology/cl-odk.yaml introducing new lines for the new slim (change \"XXX\" to the subset label): - id: XXX_upper_slim - filename: XXX_upper_slim.owl use_template: True templates: - XXX_upper_slim.csv ![image](https://github.com/obophenotype/cell-ontology/assets/94959119/254ad25f-7bf2-4ac2-afe2-9ad067d9c1ea) 2. Generating the Slim OWL file: \u00b6 Navigate to the src/ontology file in the terminal. Make sure Docker is running. Run the command: sh run.sh make update_repo 3. Modifying the Catalog: \u00b6 Open the src/ontology/catalog-v001.xml file. Add the following line (change \"XXX\" to the subset label): ` - 4. Preparing the Upper Level Slim import to CL: \u00b6 Open src/ontology/cl-edit.owl . Add the following import statement (change \"XXX\" to the subset label): Import() 5. Updating the slim owl file: \u00b6 Run the command: sh run.sh make all_subsets -B - Alternatively, run the following command to run it anyway even if it says it is up to date (change \"XXX\" to the subset label): sh run.sh make components/XXX_upper_slim.owl -B 6. Testing Slim Coverage: \u00b6 Open src/ontology/cl.Makefile . Add the subset label to SLIM_TEMPLATES (without _upper_slim!!!). Add the term that will be used to test coverage Add: $(REPORTDIR)/XXX_upper_slim.csv: $(TEMPLATEDIR)/XXX_upper_slim.csv $(eval TERM_ID := $(YYY)) $(COVERAGECMD) (substitute \"XXX\" to the subset label and YYY for the tested label) ![image](https://github.com/obophenotype/cell-ontology/assets/94959119/7eb18255-0ef7-4fbc-9f7f-e582372165bf) Using the terminal, navigate to src/ontology . Run the command: sh run.sh make slim_coverage Understanding reports \u00b6 The reports can be accessed at src/ontology/reports/XXX_upper_slim.csv . First, the coverage percentage is displayed, indicating the proportion of cells covered. Secondly, the number of cells covered by each term of the subset is provided. Finally, a list is presented, indicating all the terms that were expected to be covered but are not currently included. Ideally, terms would have more than 1 cell covered. Furthermore, a term covering hundreds of cells might indicate that it is too general, and a more specific term (or multiple) should be evaluated, specially if it overlaps with other terms of the subset. Example: For the eye_upper_slim, 'retinal cell' is a (too) general term that overlaps other grouping terms such as 'retinal bipolar neuron', 'retina horizontal cell' or 'amacrine cell'. In the case that there is overlapping of terms (term A in the subset covers term B of the subset), a coverage file will be created and it can be accessed at src/ontology/reports/overlapping_terms_XXX_upper_slim.csv .","title":"SOP for adding a new slim:"},{"location":"Create_upper_level_slim/#sop-for-adding-a-new-slim","text":"","title":"SOP for adding a new slim:"},{"location":"Create_upper_level_slim/#intro","text":"An upper slim is a set of terms for summarising annotations. CL has both a general slim and domain-specific slims allowing for the generation of general summaries or for domain specific ones. All slims have a context that covers all cells in the domain of interest. In order to fullfil the summarising use-case, a slim should have good % coverage of the domain and, if possible, avoid excluding major cell types. The following are potentially a problem for the grouping use-case: Classes in the slim with very small numbers of subclasses: Having 0-1 subclass => no capacity to summarise. Classes with disproportionately large numbers of subclasses. Overlapping classes - these clash with some types of summary - e.g. pie charts. Potential clashing concerns: It seems reasonable to want to make sure that very important cell types are covered and are not obscured in generating summaries, but this desire can clash with the considerations above. Some judgement is needed to balance these concerns. There is no perfect solution, but some solutions are better (at fulfilling the use case) than others.","title":"Intro"},{"location":"Create_upper_level_slim/#name-and-definition","text":"It is important that the name of the slim accurately reflects the content it covers. Specifically, if the intention is to cover all cell types that are specific for an organ, the slim's name should be in the format of \"organ_upper_slim\". This ensures clarity and helps users understand the scope of the slim. Furthermore, the description of the slim should follow a consistent pattern. It is recommended to use the following structure: \"a subset of general classes related to specific cell types in the [organ or specific context]\". This format provides a concise and informative description of the slim, helping users identify its purpose and content.","title":"Name and definition"},{"location":"Create_upper_level_slim/#what-files-to-create-and-edit","text":"","title":"What files to create and edit"},{"location":"Create_upper_level_slim/#1-preparing-the-subset","text":"Create XXX_upper_slim in Protege (change \"XXX\" to the subset label). See Adding a new Subset . Create a CSV table with the following characteristics (Find examples in src/templates ): 3 columns ID subset label ID AI oboInOwl:inSubset CL:####### http://purl.obolibrary.org/obo/cl#XXX_upper_slim CL term ... http://purl.obolibrary.org/obo/cl#XXX_upper_slim ... Save the CSV file with the name 'XXX_upper_slim.csv' in the src/templates directory. Modify src/ontology/cl-odk.yaml introducing new lines for the new slim (change \"XXX\" to the subset label): - id: XXX_upper_slim - filename: XXX_upper_slim.owl use_template: True templates: - XXX_upper_slim.csv ![image](https://github.com/obophenotype/cell-ontology/assets/94959119/254ad25f-7bf2-4ac2-afe2-9ad067d9c1ea)","title":"1. Preparing the subset:"},{"location":"Create_upper_level_slim/#2-generating-the-slim-owl-file","text":"Navigate to the src/ontology file in the terminal. Make sure Docker is running. Run the command: sh run.sh make update_repo","title":"2. Generating the Slim OWL file:"},{"location":"Create_upper_level_slim/#3-modifying-the-catalog","text":"Open the src/ontology/catalog-v001.xml file. Add the following line (change \"XXX\" to the subset label): ` -","title":"3. Modifying the Catalog:"},{"location":"Create_upper_level_slim/#4-preparing-the-upper-level-slim-import-to-cl","text":"Open src/ontology/cl-edit.owl . Add the following import statement (change \"XXX\" to the subset label): Import()","title":"4. Preparing the Upper Level Slim import to CL:"},{"location":"Create_upper_level_slim/#5-updating-the-slim-owl-file","text":"Run the command: sh run.sh make all_subsets -B - Alternatively, run the following command to run it anyway even if it says it is up to date (change \"XXX\" to the subset label): sh run.sh make components/XXX_upper_slim.owl -B","title":"5. Updating the slim owl file:"},{"location":"Create_upper_level_slim/#6-testing-slim-coverage","text":"Open src/ontology/cl.Makefile . Add the subset label to SLIM_TEMPLATES (without _upper_slim!!!). Add the term that will be used to test coverage Add: $(REPORTDIR)/XXX_upper_slim.csv: $(TEMPLATEDIR)/XXX_upper_slim.csv $(eval TERM_ID := $(YYY)) $(COVERAGECMD) (substitute \"XXX\" to the subset label and YYY for the tested label) ![image](https://github.com/obophenotype/cell-ontology/assets/94959119/7eb18255-0ef7-4fbc-9f7f-e582372165bf) Using the terminal, navigate to src/ontology . Run the command: sh run.sh make slim_coverage","title":"6. Testing Slim Coverage:"},{"location":"Create_upper_level_slim/#understanding-reports","text":"The reports can be accessed at src/ontology/reports/XXX_upper_slim.csv . First, the coverage percentage is displayed, indicating the proportion of cells covered. Secondly, the number of cells covered by each term of the subset is provided. Finally, a list is presented, indicating all the terms that were expected to be covered but are not currently included. Ideally, terms would have more than 1 cell covered. Furthermore, a term covering hundreds of cells might indicate that it is too general, and a more specific term (or multiple) should be evaluated, specially if it overlaps with other terms of the subset. Example: For the eye_upper_slim, 'retinal cell' is a (too) general term that overlaps other grouping terms such as 'retinal bipolar neuron', 'retina horizontal cell' or 'amacrine cell'. In the case that there is overlapping of terms (term A in the subset covers term B of the subset), a coverage file will be created and it can be accessed at src/ontology/reports/overlapping_terms_XXX_upper_slim.csv .","title":"Understanding reports"},{"location":"Fixing_xsdstring_diffs/","text":"Fixing ^^xsd:string Diffs \u00b6 When you make edits, sometimes there will be large amounts of unintended differences that show up that involves the removal of ^^xsd:string . If so, you can resolve them by following normalising your cl-edit.owl file. SOP \u00b6 Update your file from Master (see 'How to resolve merge conflicts' for instructions on how to do this including how to resolve clashes while doing this). in the terminal, set directory to the ontology folder in CL: cd .../GitHub/cell-ontology/src/ontology Run the normaliser in terminal: If you have docker installed: sh run.sh make normalise_xsd_string If you do not have docker installed: make normalise_xsd_string If make is not installed, on MAC: sed -i '' -E \"s/Annotation[(](oboInOwl[:]hasDbXref [\\\"][^\\\"]*[\\\"])[)]/Annotation(\\1^^xsd:string)/g\" cl-edit.owl This should resolve your ^^xsd:string issue, after which, you can handle your pull request as per usual.","title":"Fixing unintended ^^xsd:string diffs"},{"location":"Fixing_xsdstring_diffs/#fixing-xsdstring-diffs","text":"When you make edits, sometimes there will be large amounts of unintended differences that show up that involves the removal of ^^xsd:string . If so, you can resolve them by following normalising your cl-edit.owl file.","title":"Fixing ^^xsd:string Diffs"},{"location":"Fixing_xsdstring_diffs/#sop","text":"Update your file from Master (see 'How to resolve merge conflicts' for instructions on how to do this including how to resolve clashes while doing this). in the terminal, set directory to the ontology folder in CL: cd .../GitHub/cell-ontology/src/ontology Run the normaliser in terminal: If you have docker installed: sh run.sh make normalise_xsd_string If you do not have docker installed: make normalise_xsd_string If make is not installed, on MAC: sed -i '' -E \"s/Annotation[(](oboInOwl[:]hasDbXref [\\\"][^\\\"]*[\\\"])[)]/Annotation(\\1^^xsd:string)/g\" cl-edit.owl This should resolve your ^^xsd:string issue, after which, you can handle your pull request as per usual.","title":"SOP"},{"location":"Keeping_ontology_terms_up_to_date/","text":"Keeping cell ontology annotation up to date \u00b6 Cell ontology identifiers (IRIs) are never lost, but they are occasionally deprecated. On the rare occasions that this happens, all logical links to other ontology terms (e.g. recording classification or partonomy) are removed and term is tagged with the annotation owl:deprecated True . To aid migration of annotations to the latest standard, these terms are also annotated with either a term_replaced_by or a consider tag. A term_replaced_by annotation is used to record the ID of a term it is safe to auto-migrate annotations to. More rarely, consider is used to record multiple potential replacement terms requiring human consideration to map. In these cases, a comment will be present to provide guidance for mapping. The Ontology Lookup Service API provides a convenient way to check for deprecated terms & find replacements. The term http://purl.obolibrary.org/obo/CL_0000375 has been deprecated and has that tag term_replaced_by Querying the OLS API for this: https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375. (Note the query IRI must be double encoded) Returns: { \"iri\" : \"http://purl.obolibrary.org/obo/CL_0000375\", \"label\" : \"obsolete osteoprogenitor cell\", \"description\" : null, \"annotation\" : { \"database_cross_reference\" : [ \"BTO:0002051\" ], \"has_obo_namespace\" : [ \"cell\" ], \"term replaced by\" : [ \"CL:0007010\" ] }, \"synonyms\" : null, \"ontology_name\" : \"cl\", \"ontology_prefix\" : \"CL\", \"ontology_iri\" : \"http://purl.obolibrary.org/obo/cl.owl\", \"is_obsolete\" : true, \"term_replaced_by\" : \"CL:0007010\", \"is_defining_ontology\" : true, \"has_children\" : false, \"is_root\" : true, \"short_form\" : \"CL_0000375\", \"obo_id\" : \"CL:0000375\", \"in_subset\" : null, \"obo_definition_citation\" : null, \"obo_xref\" : [{\"database\":\"BTO\",\"id\":\"0002051\",\"description\":null,\"url\":\"http://purl.obolibrary.org/obo/BTO_0002051\"}], \"obo_synonym\" : null, \"is_preferred_root\" : false, \"_links\" : { \"self\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375\" }, \"graph\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375/graph\" } } } The term_replaced_by key points to the ID of a safe replacement term: CL:0007010. This is a CURIE for http://purl.obolibrary.org/obo/CL_0007010 * consider The term http://purl.obolibrary.org/obo/CL_0000144 has been deprecated and has a consider tag pointing to multiple possible replacement terms, along with a comment for guidance. Querying the OLS API for this: https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144 Returns { \"iri\" : \"http://purl.obolibrary.org/obo/CL_0000144\", \"label\" : \"obsolete cell by function\", \"description\" : [ \"OBSOLETE: A classification of cells by their primary end goal or behavior.\" ], \"annotation\" : { \"comment\" : [ \"This term was made obsolete because there is no difference in meaning between it and 'cell', as any cell can be classified by its function or behavior. If you have used this term in annotation, please replace it with cell (CL:0000000), native cell (CL:0000003), or cell in vitro (CL:0001034) as appropriate.\" ], \"consider\" : [ \"CL:0001034\", \"CL:0000000\", \"CL:0000003\" ], \"has_obo_namespace\" : [ \"cell\" ] }, \"synonyms\" : null, \"ontology_name\" : \"cl\", \"ontology_prefix\" : \"CL\", \"ontology_iri\" : \"http://purl.obolibrary.org/obo/cl.owl\", \"is_obsolete\" : true, \"term_replaced_by\" : null, \"is_defining_ontology\" : true, \"has_children\" : false, \"is_root\" : true, \"short_form\" : \"CL_0000144\", \"obo_id\" : \"CL:0000144\", \"in_subset\" : null, \"obo_definition_citation\" : [{\"definition\":\"OBSOLETE: A classification of cells by their primary end goal or behavior.\",\"oboXrefs\":[{\"database\":\"FB\",\"id\":\"ma\",\"description\":null,\"url\":\"http://flybase.org/reports/ma.html\"}]}], \"obo_xref\" : null, \"obo_synonym\" : null, \"is_preferred_root\" : false, \"_links\" : { \"self\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144\" }, \"graph\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144/graph\" } } } * Warning - due to legacy issues, the values of these tags are either a curie (CL:0000123) or short_form ID (CL_0000123) rather than an iri. Handling code needs to deal with both of these formats.","title":"Keep terms up-to-date"},{"location":"Keeping_ontology_terms_up_to_date/#keeping-cell-ontology-annotation-up-to-date","text":"Cell ontology identifiers (IRIs) are never lost, but they are occasionally deprecated. On the rare occasions that this happens, all logical links to other ontology terms (e.g. recording classification or partonomy) are removed and term is tagged with the annotation owl:deprecated True . To aid migration of annotations to the latest standard, these terms are also annotated with either a term_replaced_by or a consider tag. A term_replaced_by annotation is used to record the ID of a term it is safe to auto-migrate annotations to. More rarely, consider is used to record multiple potential replacement terms requiring human consideration to map. In these cases, a comment will be present to provide guidance for mapping. The Ontology Lookup Service API provides a convenient way to check for deprecated terms & find replacements. The term http://purl.obolibrary.org/obo/CL_0000375 has been deprecated and has that tag term_replaced_by Querying the OLS API for this: https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375. (Note the query IRI must be double encoded) Returns: { \"iri\" : \"http://purl.obolibrary.org/obo/CL_0000375\", \"label\" : \"obsolete osteoprogenitor cell\", \"description\" : null, \"annotation\" : { \"database_cross_reference\" : [ \"BTO:0002051\" ], \"has_obo_namespace\" : [ \"cell\" ], \"term replaced by\" : [ \"CL:0007010\" ] }, \"synonyms\" : null, \"ontology_name\" : \"cl\", \"ontology_prefix\" : \"CL\", \"ontology_iri\" : \"http://purl.obolibrary.org/obo/cl.owl\", \"is_obsolete\" : true, \"term_replaced_by\" : \"CL:0007010\", \"is_defining_ontology\" : true, \"has_children\" : false, \"is_root\" : true, \"short_form\" : \"CL_0000375\", \"obo_id\" : \"CL:0000375\", \"in_subset\" : null, \"obo_definition_citation\" : null, \"obo_xref\" : [{\"database\":\"BTO\",\"id\":\"0002051\",\"description\":null,\"url\":\"http://purl.obolibrary.org/obo/BTO_0002051\"}], \"obo_synonym\" : null, \"is_preferred_root\" : false, \"_links\" : { \"self\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375\" }, \"graph\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000375/graph\" } } } The term_replaced_by key points to the ID of a safe replacement term: CL:0007010. This is a CURIE for http://purl.obolibrary.org/obo/CL_0007010 * consider The term http://purl.obolibrary.org/obo/CL_0000144 has been deprecated and has a consider tag pointing to multiple possible replacement terms, along with a comment for guidance. Querying the OLS API for this: https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144 Returns { \"iri\" : \"http://purl.obolibrary.org/obo/CL_0000144\", \"label\" : \"obsolete cell by function\", \"description\" : [ \"OBSOLETE: A classification of cells by their primary end goal or behavior.\" ], \"annotation\" : { \"comment\" : [ \"This term was made obsolete because there is no difference in meaning between it and 'cell', as any cell can be classified by its function or behavior. If you have used this term in annotation, please replace it with cell (CL:0000000), native cell (CL:0000003), or cell in vitro (CL:0001034) as appropriate.\" ], \"consider\" : [ \"CL:0001034\", \"CL:0000000\", \"CL:0000003\" ], \"has_obo_namespace\" : [ \"cell\" ] }, \"synonyms\" : null, \"ontology_name\" : \"cl\", \"ontology_prefix\" : \"CL\", \"ontology_iri\" : \"http://purl.obolibrary.org/obo/cl.owl\", \"is_obsolete\" : true, \"term_replaced_by\" : null, \"is_defining_ontology\" : true, \"has_children\" : false, \"is_root\" : true, \"short_form\" : \"CL_0000144\", \"obo_id\" : \"CL:0000144\", \"in_subset\" : null, \"obo_definition_citation\" : [{\"definition\":\"OBSOLETE: A classification of cells by their primary end goal or behavior.\",\"oboXrefs\":[{\"database\":\"FB\",\"id\":\"ma\",\"description\":null,\"url\":\"http://flybase.org/reports/ma.html\"}]}], \"obo_xref\" : null, \"obo_synonym\" : null, \"is_preferred_root\" : false, \"_links\" : { \"self\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144\" }, \"graph\" : { \"href\" : \"https://www.ebi.ac.uk/ols/api/ontologies/cl/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FCL_0000144/graph\" } } } * Warning - due to legacy issues, the values of these tags are either a curie (CL:0000123) or short_form ID (CL_0000123) rather than an iri. Handling code needs to deal with both of these formats.","title":"Keeping cell ontology annotation up to date"},{"location":"annotation_properties/","text":"Annotation Properties \u00b6 Note- this page is currently under development. The Cell Ontology has the following annotation properties: Annotation property Description Example term Example annotation Must have? Only one use per term is allowed? consider To be used on obsoleted classes, to point to a term that should be considered by curators for use in place of the obsoleted term. Multiple consider terms are allowed. It can be useful to combine this with a comment to indicate when replacement would be appropriate. CL:0000610 obsolete plant cell PO:0009002 No No created_by Added automatically on term creation with standard Protege settings. Ideally, this should use the \"supplied user name\" in the Protege User Details preference pane. This has been inconsistently applied in the past. CL:0002518 tmeehan Should Yes creation_date Added automatically on term creation with standard Protege settings. CL:0002518 2011-02-08T10:46:34Z Should Yes database_cross_reference Citable references that have helped generate the term and term's definition. Includes PubMed IDs (in the format PMID:XXXXXXXX). CL:0011005 GABAergic interneuron PMID:29724907 Should No dc:contributor Use this to annotate a whole ontology file with the identifier of a contributor. ORCID preferred. N/A https://orcid.org/0000-0001-9990-8331 Nice to have, if applicable No dc:creator Coming Soon CL:0001201 B cell, CD19-positive https://orcid.org/0000-0001-9990-8331 No Yes dc:date Coming soon CL:0001065 innate lymphoid cell 2017-01-30T20:20:48Z No Yes dc:description Use this to annotate a whole ontology file with a brief description of the ontology. N/A An ontology of cell types. No No dc:title Use this to annotate an ontology, giving it a human readable title. N/A Cell Ontology No No dcterms:license Use to attach a license to the whole ontology file. N/A http://creativecommons.org/licenses/by/4.0/ No No definition The textual definition for the ontology class. CL:0000946 antibody secreting cell A lymphocyte of B lineage that is devoted to secreting large amounts of immunoglobulin. Must Yes 'expand expression to' Coming soon Coming soon Coming soon No No foaf:depicted_by Use this to add a link to an image that depicts an example of an entity referred to by the term Coming soon Coming soon No No has_alternative_id In CL this is a legacy property. Do not use. CL:0000059 ameloblast CL:0000053 No No has_broad_synonym Used for synonyms where the primary definition accurately describes the synonym, but the definition of the synonym may encompass other structures as well. In some cases where a broad synonym is given, it will be a broad synonym for more than one ontology term. You are encouraged to add a reference that uses the term in this way. CL:0000365 animal zygote zygote No No has_exact_synonym Used for synonyms where the definition of the synonym is exactly the same as primary term definition. This is used when the same class can have more than one name. You are encouraged to add a reference that uses the term in this way. CL:0000622 acinar cell acinic cell Nice to have, if applicable No has_narrow_synonym Used for synonyms where the definition of the synonym is the same as the primary definition, but has additional qualifiers. You are encouraged to add a reference that uses the term in this way. CL:0000362 epidermal cell epithelial cell of skin No No has_obo_namespace This is a legacy annotation property. Do not add this manually. CL:0001061 abnormal cell cell No No has_related_synonym This scope is applied when a word of phrase has been used synonymously with the primary term name in the literature, but the usage is not strictly correct. That is, the synonym in fact has a slightly different meaning than the primary term name. Since users may not be aware that the synonym was being used incorrectly when searching for a term, related synonyms are included. CL:0000902 induced T-regulatory cell adaptive Treg No No has_synonym_type The target of this relation must be an annotation property of type 'synonym_type_property'. N/A N/A No No IAO_0000116 Coming soon Coming soon Coming soon No No id Automatically added by some pathways. Do not add manually. If duplicating a term (with the duplicate getting a new ID), it should be deleted. CL:2000074 splenocyte CL:2000074 Yes Yes in_subset Used to add subset tags, used in conjunction with subset_property CL:0000039 germ line cell _upper_level No No is_inferred This annotation property is used in some automated pipelines. Do not add manually Coming soon Coming soon No No rdfs:comment Use to add a clarifying comment to a term. This can be useful for adding examples and for clarifying terminological confusions. CL:0007016 adaxial cell In teleosts, adaxial cells give rise to slow muscle myoblasts. No Yes rdfs:isDefinedBy Do not add manually. Coming soon Coming soon No Yes rdfs:label Primary name - used as a display name by Protege (with standard settings) and most downstream consumers. Add only one of these. It must be unique within an ontology. CL:0000418 arcade cell arcade cell Must Yes RO_0002161 Coming soon Coming soon No No 'see also' Used to link to a webpage, such as a GitHub ticket. CL:0000134 mesenchymal stem cell https://github.com/obophenotype/cell-ontology/issues/474 No No shorthand Added automatically by some pipelines. Do not add manually Coming soon Coming soon No No subset_property A grouping class for subset tags. N/A N/A No No subset_property: added_for_HCA A subset tag for terms that were requested by the Human Cell Atlas. N/A N/A No No subset property: location_grouping A subset tag for cell types from a particular anatomical location. N/A N/A No No synonym_type_property A grouping class for synonym tags. N/A N/A No No 'term replaced by' To be used on obsolete terms to indicate a term that can be automatically substituted for the obsoleted term. Coming soon Coming soon No No","title":"CL annotation properties"},{"location":"annotation_properties/#annotation-properties","text":"Note- this page is currently under development. The Cell Ontology has the following annotation properties: Annotation property Description Example term Example annotation Must have? Only one use per term is allowed? consider To be used on obsoleted classes, to point to a term that should be considered by curators for use in place of the obsoleted term. Multiple consider terms are allowed. It can be useful to combine this with a comment to indicate when replacement would be appropriate. CL:0000610 obsolete plant cell PO:0009002 No No created_by Added automatically on term creation with standard Protege settings. Ideally, this should use the \"supplied user name\" in the Protege User Details preference pane. This has been inconsistently applied in the past. CL:0002518 tmeehan Should Yes creation_date Added automatically on term creation with standard Protege settings. CL:0002518 2011-02-08T10:46:34Z Should Yes database_cross_reference Citable references that have helped generate the term and term's definition. Includes PubMed IDs (in the format PMID:XXXXXXXX). CL:0011005 GABAergic interneuron PMID:29724907 Should No dc:contributor Use this to annotate a whole ontology file with the identifier of a contributor. ORCID preferred. N/A https://orcid.org/0000-0001-9990-8331 Nice to have, if applicable No dc:creator Coming Soon CL:0001201 B cell, CD19-positive https://orcid.org/0000-0001-9990-8331 No Yes dc:date Coming soon CL:0001065 innate lymphoid cell 2017-01-30T20:20:48Z No Yes dc:description Use this to annotate a whole ontology file with a brief description of the ontology. N/A An ontology of cell types. No No dc:title Use this to annotate an ontology, giving it a human readable title. N/A Cell Ontology No No dcterms:license Use to attach a license to the whole ontology file. N/A http://creativecommons.org/licenses/by/4.0/ No No definition The textual definition for the ontology class. CL:0000946 antibody secreting cell A lymphocyte of B lineage that is devoted to secreting large amounts of immunoglobulin. Must Yes 'expand expression to' Coming soon Coming soon Coming soon No No foaf:depicted_by Use this to add a link to an image that depicts an example of an entity referred to by the term Coming soon Coming soon No No has_alternative_id In CL this is a legacy property. Do not use. CL:0000059 ameloblast CL:0000053 No No has_broad_synonym Used for synonyms where the primary definition accurately describes the synonym, but the definition of the synonym may encompass other structures as well. In some cases where a broad synonym is given, it will be a broad synonym for more than one ontology term. You are encouraged to add a reference that uses the term in this way. CL:0000365 animal zygote zygote No No has_exact_synonym Used for synonyms where the definition of the synonym is exactly the same as primary term definition. This is used when the same class can have more than one name. You are encouraged to add a reference that uses the term in this way. CL:0000622 acinar cell acinic cell Nice to have, if applicable No has_narrow_synonym Used for synonyms where the definition of the synonym is the same as the primary definition, but has additional qualifiers. You are encouraged to add a reference that uses the term in this way. CL:0000362 epidermal cell epithelial cell of skin No No has_obo_namespace This is a legacy annotation property. Do not add this manually. CL:0001061 abnormal cell cell No No has_related_synonym This scope is applied when a word of phrase has been used synonymously with the primary term name in the literature, but the usage is not strictly correct. That is, the synonym in fact has a slightly different meaning than the primary term name. Since users may not be aware that the synonym was being used incorrectly when searching for a term, related synonyms are included. CL:0000902 induced T-regulatory cell adaptive Treg No No has_synonym_type The target of this relation must be an annotation property of type 'synonym_type_property'. N/A N/A No No IAO_0000116 Coming soon Coming soon Coming soon No No id Automatically added by some pathways. Do not add manually. If duplicating a term (with the duplicate getting a new ID), it should be deleted. CL:2000074 splenocyte CL:2000074 Yes Yes in_subset Used to add subset tags, used in conjunction with subset_property CL:0000039 germ line cell _upper_level No No is_inferred This annotation property is used in some automated pipelines. Do not add manually Coming soon Coming soon No No rdfs:comment Use to add a clarifying comment to a term. This can be useful for adding examples and for clarifying terminological confusions. CL:0007016 adaxial cell In teleosts, adaxial cells give rise to slow muscle myoblasts. No Yes rdfs:isDefinedBy Do not add manually. Coming soon Coming soon No Yes rdfs:label Primary name - used as a display name by Protege (with standard settings) and most downstream consumers. Add only one of these. It must be unique within an ontology. CL:0000418 arcade cell arcade cell Must Yes RO_0002161 Coming soon Coming soon No No 'see also' Used to link to a webpage, such as a GitHub ticket. CL:0000134 mesenchymal stem cell https://github.com/obophenotype/cell-ontology/issues/474 No No shorthand Added automatically by some pipelines. Do not add manually Coming soon Coming soon No No subset_property A grouping class for subset tags. N/A N/A No No subset_property: added_for_HCA A subset tag for terms that were requested by the Human Cell Atlas. N/A N/A No No subset property: location_grouping A subset tag for cell types from a particular anatomical location. N/A N/A No No synonym_type_property A grouping class for synonym tags. N/A N/A No No 'term replaced by' To be used on obsolete terms to indicate a term that can be automatically substituted for the obsoleted term. Coming soon Coming soon No No","title":"Annotation Properties"},{"location":"cite/","text":"How to cite CL \u00b6 The Cell Ontology 2016: enhanced content, modularization, and ontology interoperability. Alexander D Diehl, Terrence F Meehan, Yvonne M Bradford, Matthew H Brush, Wasila M Dahdul, David S Dougall, Yongqun He, David Osumi-Sutherland, Alan Ruttenberg, Sirarat Sarntivijai, Ceri E Van Slyke, Nicole A Vasilevsky, Melissa A Haendel, Judith A Blake, Christopher J Mungall. J Biomed Semantics. 2016 Jul 4;7(1):44. PMID:27377652, PMCID:PMC4932724, DOI:10.1186/s13326-016-0088-7 Logical development of the cell ontology. Terrence F Meehan, Anna Maria Masci, Amina Abdulla, Lindsay G Cowell, Judith A Blake, Christopher J Mungall, Alexander D Diehl. BMC Bioinformatics. 2011 Jan 5;12:6. PMID:21208450, PMCID:PMC3024222, DOI:10.1186/1471-2105-12-6 An ontology for cell types. Jonathan Bard, Seung Y Rhee, Michael Ashburner. Genome Biol. 2005;6(2):R21. PMID:15693950, PMCID:PMC551541, DOI:10.1186/gb-2005-6-2-r21","title":"Cite"},{"location":"cite/#how-to-cite-cl","text":"The Cell Ontology 2016: enhanced content, modularization, and ontology interoperability. Alexander D Diehl, Terrence F Meehan, Yvonne M Bradford, Matthew H Brush, Wasila M Dahdul, David S Dougall, Yongqun He, David Osumi-Sutherland, Alan Ruttenberg, Sirarat Sarntivijai, Ceri E Van Slyke, Nicole A Vasilevsky, Melissa A Haendel, Judith A Blake, Christopher J Mungall. J Biomed Semantics. 2016 Jul 4;7(1):44. PMID:27377652, PMCID:PMC4932724, DOI:10.1186/s13326-016-0088-7 Logical development of the cell ontology. Terrence F Meehan, Anna Maria Masci, Amina Abdulla, Lindsay G Cowell, Judith A Blake, Christopher J Mungall, Alexander D Diehl. BMC Bioinformatics. 2011 Jan 5;12:6. PMID:21208450, PMCID:PMC3024222, DOI:10.1186/1471-2105-12-6 An ontology for cell types. Jonathan Bard, Seung Y Rhee, Michael Ashburner. Genome Biol. 2005;6(2):R21. PMID:15693950, PMCID:PMC551541, DOI:10.1186/gb-2005-6-2-r21","title":"How to cite CL"},{"location":"cl-release/","text":"CL Release workflow \u00b6 While CL is an ODK ontology, it has a specific workflow for releases due to its large size and the limitations on standard GitHub releases. Requirements \u00b6 Aside from the standard requirements needed for ODK workflow, GH is required. Instructions on how to install GH can be found here You will need to log in to your GitHub account on GH before you can uses it. To do this, enter the following in your terminal: gh auth login You can then follow instructions below for web browser login (or use your prefer means of logging in). % gh auth login ? What account do you want to log into? GitHub.com ? What is your preferred protocol for Git operations? SSH ? Generate a new SSH key to add to your GitHub account? Yes ? Enter a passphrase for your new SSH key (Optional) ? Title for your SSH key: GitHub SSH ? How would you like to authenticate GitHub CLI? Login with a web browser ! First copy your one-time code: XXXX-XXXX Press Enter to open github.com in your browser... \u2713 Authentication complete. - gh config set -h github.com git_protocol ssh \u2713 Configured git protocol \u2713 Uploaded the SSH key to your GitHub account: /Users/username/.ssh/id_ed25519.pub \u2713 Logged in as username Release Process \u00b6 The release is done in two parts: 1. Refresh the imports and the components (CellxGene subset, HRA subset and slims) 1. Generate the release artefacts Preparation \u00b6 Ensure that all pull requests to be included in the release are merged Ensure that no other pull requests are merged during the release process Ensure you are on the master branch and have locally the latest changes from master ( git pull ) Ensure you have the latest ODK installed by running docker pull obolibrary/odkfull Navigate to the cell-ontology/src/ontology directory ( cd src/ontology ) Refresh imports and components \u00b6 Checkout a new branch (e.g. git checkout -b refresh-imports-sept24 ) Run sh run.sh make refresh-imports Have a sanity check in the files Create a pull request and add at least a core editor as the reviewer Merge to main branch once reviewed and CI checks have passed Generate the release artefacts \u00b6 Ensure you are on the master branch and have locally the latest changes from master ( git pull ) Checkout a new branch (e.g. git checkout -b 20240904-release ) Run the release using sh run.sh make cl DEPLOY_GH=false . This will build all files and copy them to the correct place. Review the release as per the Review the release section in ODK-workflow release document Create a pull request and get a second set of eyes to review it. As CL uses a custom release pipeline, we ask that you get at least one core developer to review it too. Merge to main branch once reviewed and CI checks have passed Deploy release on GitHub by running make deploy_release GHVERSION=\"v2022-06-20\" on the release branch (DO NOTE CHANGE TO MAIN BRANCH!), replacing the date with the date of release (NOTE: no sh run.sh ) This should end with a GitHub release link that looks something like: https://github.com/obophenotype/cl/releases/tag/untagged-8935f3432525b27a0d84 . Copy the link and paste it in your browser, this should show you a draft release. Click the edit button (the pencil button on the top right corner) and change the tag to the GHVERSION you entered above (eg v2022-06-20) Change the TBD. in the main text to a summary of the main changes in the release if needed. Copy and paste the text and table from the reports/summary_release.md file. This file is in .gitignore and will only be available to those who have run the release. Scroll down all the way and click the update release button.","title":"CL release"},{"location":"cl-release/#cl-release-workflow","text":"While CL is an ODK ontology, it has a specific workflow for releases due to its large size and the limitations on standard GitHub releases.","title":"CL Release workflow"},{"location":"cl-release/#requirements","text":"Aside from the standard requirements needed for ODK workflow, GH is required. Instructions on how to install GH can be found here You will need to log in to your GitHub account on GH before you can uses it. To do this, enter the following in your terminal: gh auth login You can then follow instructions below for web browser login (or use your prefer means of logging in). % gh auth login ? What account do you want to log into? GitHub.com ? What is your preferred protocol for Git operations? SSH ? Generate a new SSH key to add to your GitHub account? Yes ? Enter a passphrase for your new SSH key (Optional) ? Title for your SSH key: GitHub SSH ? How would you like to authenticate GitHub CLI? Login with a web browser ! First copy your one-time code: XXXX-XXXX Press Enter to open github.com in your browser... \u2713 Authentication complete. - gh config set -h github.com git_protocol ssh \u2713 Configured git protocol \u2713 Uploaded the SSH key to your GitHub account: /Users/username/.ssh/id_ed25519.pub \u2713 Logged in as username","title":"Requirements"},{"location":"cl-release/#release-process","text":"The release is done in two parts: 1. Refresh the imports and the components (CellxGene subset, HRA subset and slims) 1. Generate the release artefacts","title":"Release Process"},{"location":"cl-release/#preparation","text":"Ensure that all pull requests to be included in the release are merged Ensure that no other pull requests are merged during the release process Ensure you are on the master branch and have locally the latest changes from master ( git pull ) Ensure you have the latest ODK installed by running docker pull obolibrary/odkfull Navigate to the cell-ontology/src/ontology directory ( cd src/ontology )","title":"Preparation"},{"location":"cl-release/#refresh-imports-and-components","text":"Checkout a new branch (e.g. git checkout -b refresh-imports-sept24 ) Run sh run.sh make refresh-imports Have a sanity check in the files Create a pull request and add at least a core editor as the reviewer Merge to main branch once reviewed and CI checks have passed","title":"Refresh imports and components"},{"location":"cl-release/#generate-the-release-artefacts","text":"Ensure you are on the master branch and have locally the latest changes from master ( git pull ) Checkout a new branch (e.g. git checkout -b 20240904-release ) Run the release using sh run.sh make cl DEPLOY_GH=false . This will build all files and copy them to the correct place. Review the release as per the Review the release section in ODK-workflow release document Create a pull request and get a second set of eyes to review it. As CL uses a custom release pipeline, we ask that you get at least one core developer to review it too. Merge to main branch once reviewed and CI checks have passed Deploy release on GitHub by running make deploy_release GHVERSION=\"v2022-06-20\" on the release branch (DO NOTE CHANGE TO MAIN BRANCH!), replacing the date with the date of release (NOTE: no sh run.sh ) This should end with a GitHub release link that looks something like: https://github.com/obophenotype/cl/releases/tag/untagged-8935f3432525b27a0d84 . Copy the link and paste it in your browser, this should show you a draft release. Click the edit button (the pencil button on the top right corner) and change the tag to the GHVERSION you entered above (eg v2022-06-20) Change the TBD. in the main text to a summary of the main changes in the release if needed. Copy and paste the text and table from the reports/summary_release.md file. This file is in .gitignore and will only be available to those who have run the release. Scroll down all the way and click the update release button.","title":"Generate the release artefacts"},{"location":"contact_us/","text":"Contact Us \u00b6 GitHub \u00b6 The preferred point of contact for the Cell Ontology is the GitHub Issue Tracker . It is used commonly for requesting new terms or suggesting changes to existing terms. Feel free to open a new issue with any kind of request/question/consideration you might have. If you do not have a GitHub account, you can sign up for a free account here . E-mail and other contacts \u00b6 The Cell Ontology also has a Slack Channel and monthly meetings, with an open agenda . If you want to join the channel or talk about any other topics related to the Cell Ontology, you may reach out to one of the active CL editors. The official contact for CL listed at the OBO Foundry is Alexander Diehl , but feel free to contact any other individual. A list of active CL editors can be found here . Note that requests on the GitHub issue tracker may be addressed more quickly, as individuals are often busy. For other details on CL, you may see http://obofoundry.org/ontology/cl.html .","title":"Contact"},{"location":"contact_us/#contact-us","text":"","title":"Contact Us"},{"location":"contact_us/#github","text":"The preferred point of contact for the Cell Ontology is the GitHub Issue Tracker . It is used commonly for requesting new terms or suggesting changes to existing terms. Feel free to open a new issue with any kind of request/question/consideration you might have. If you do not have a GitHub account, you can sign up for a free account here .","title":"GitHub"},{"location":"contact_us/#e-mail-and-other-contacts","text":"The Cell Ontology also has a Slack Channel and monthly meetings, with an open agenda . If you want to join the channel or talk about any other topics related to the Cell Ontology, you may reach out to one of the active CL editors. The official contact for CL listed at the OBO Foundry is Alexander Diehl , but feel free to contact any other individual. A list of active CL editors can be found here . Note that requests on the GitHub issue tracker may be addressed more quickly, as individuals are often busy. For other details on CL, you may see http://obofoundry.org/ontology/cl.html .","title":"E-mail and other contacts"},{"location":"contributing/","text":"How to contribute to CL \u00b6 We welcome your contributions to CL! Generally, you can follow the editors workflow instructions here . However, if you aren't confident in directly editing the ontology, you can contribute by writing up an issue and one of our curators/developers will pick it up and address it. Writing up an issue \u00b6 If you want a new term added, or want edits to a current term, or spot any mistakes/issues with CL, or you have any other CL related issues, you can write up a ticket using the following steps: Go to the issues tab in CL. Click the 'New issue' tab on the top right corner and select the most appropriate category for your issue. (Note: blank issues can be created if none of the categories fit, but we recommend using the categories as they are designed to be more comprehensive). Fill up the form as best you can, giving a descriptive title to your issue name and leaving the bracketed [] tag in the title: eg. Add new term is bad name, while [NTR] larval stage X is good name. When writing up more complex issues that include multiple items or steps, make sure you include the use of - [ ] to denote action items. These turn into checkboxes which makes it much faster to assess which comments have been addressed. (Note: it is better to write up multiple issues than one big one with multiple items, e.g. write up one issue for each term you want added rather than an issue with all the terms you want added.) If you know a specific curator/editor that you want handling your ticket, you can assign them to your ticket in the assignee tab on the right, if not, someone from our team will assign an appropriate person to handle your ticket. If, however, your ticket has not been looked at in more than 10 days, and you suspect that it might have been missed, please assign it to gouttegd and they will assign it appropriately. If you know how to edit the ontology directly, please then proceed to making a Pull request with the guidelines below, following the editors workflow instructions here . Pull request guidelines \u00b6 Give your pull requests good names: Add new terms is bad. Adding larval stage X term #332 is ok. Make sure pull requests have someone assigned to review them and remind them once in a while. Do not let them go dormant Assign yourself to be the Assignee Make sure to use - [ ] to denote action items in issues and pull requests, not just comments. These turn into checkboxes which makes it much faster to assess which comments have been addressed and can be ignored. Give a short summary of the pull request - that way we can find suitable reviewers much quicker. Say which terms you are adding or what kinds of changes you are proposing. It is most of the time a good idea to use squash merge rather than merge for your pull request, to keep the git history short and useful. Pull requests that require imports to be refreshed \u00b6 If your pull request references foreign terms from an external ontology that are not yet present in the import module for that ontology (for example, you\u2019re adding a logical definition that makes use of a GO term for the first time), imports needs to be refreshed for the foreign terms to be available to use. If you have the technical skills and/or the required computer resources (refreshing imports can be a memory-intensive task), you may refresh the imports yourself before submitting the pull request, by following the appropriate procedure . If you can\u2019t apply the imports refreshing procedure for any reason, you may instead opt for using \u201cbare IRIs\u201d when editing the ontology, everywhere you need a reference to a foreign term. Then, when submitting your pull request, label it with the tag update-imports-required to ask that a member of the tech support group refresh the imports before the pull request can be merged. People reviewing pull requests must 1) make sure that if a pull request is referencing bare IRIs, the request is tagged with update-imports-required (adding the label themselves if needed); and 2) make sure that imports have indeed been updated (either by the author of the pull request, or by someone from the tech support group if requested) before allowing the request to be merged.","title":"Contributing"},{"location":"contributing/#how-to-contribute-to-cl","text":"We welcome your contributions to CL! Generally, you can follow the editors workflow instructions here . However, if you aren't confident in directly editing the ontology, you can contribute by writing up an issue and one of our curators/developers will pick it up and address it.","title":"How to contribute to CL"},{"location":"contributing/#writing-up-an-issue","text":"If you want a new term added, or want edits to a current term, or spot any mistakes/issues with CL, or you have any other CL related issues, you can write up a ticket using the following steps: Go to the issues tab in CL. Click the 'New issue' tab on the top right corner and select the most appropriate category for your issue. (Note: blank issues can be created if none of the categories fit, but we recommend using the categories as they are designed to be more comprehensive). Fill up the form as best you can, giving a descriptive title to your issue name and leaving the bracketed [] tag in the title: eg. Add new term is bad name, while [NTR] larval stage X is good name. When writing up more complex issues that include multiple items or steps, make sure you include the use of - [ ] to denote action items. These turn into checkboxes which makes it much faster to assess which comments have been addressed. (Note: it is better to write up multiple issues than one big one with multiple items, e.g. write up one issue for each term you want added rather than an issue with all the terms you want added.) If you know a specific curator/editor that you want handling your ticket, you can assign them to your ticket in the assignee tab on the right, if not, someone from our team will assign an appropriate person to handle your ticket. If, however, your ticket has not been looked at in more than 10 days, and you suspect that it might have been missed, please assign it to gouttegd and they will assign it appropriately. If you know how to edit the ontology directly, please then proceed to making a Pull request with the guidelines below, following the editors workflow instructions here .","title":"Writing up an issue"},{"location":"contributing/#pull-request-guidelines","text":"Give your pull requests good names: Add new terms is bad. Adding larval stage X term #332 is ok. Make sure pull requests have someone assigned to review them and remind them once in a while. Do not let them go dormant Assign yourself to be the Assignee Make sure to use - [ ] to denote action items in issues and pull requests, not just comments. These turn into checkboxes which makes it much faster to assess which comments have been addressed and can be ignored. Give a short summary of the pull request - that way we can find suitable reviewers much quicker. Say which terms you are adding or what kinds of changes you are proposing. It is most of the time a good idea to use squash merge rather than merge for your pull request, to keep the git history short and useful.","title":"Pull request guidelines"},{"location":"contributing/#pull-requests-that-require-imports-to-be-refreshed","text":"If your pull request references foreign terms from an external ontology that are not yet present in the import module for that ontology (for example, you\u2019re adding a logical definition that makes use of a GO term for the first time), imports needs to be refreshed for the foreign terms to be available to use. If you have the technical skills and/or the required computer resources (refreshing imports can be a memory-intensive task), you may refresh the imports yourself before submitting the pull request, by following the appropriate procedure . If you can\u2019t apply the imports refreshing procedure for any reason, you may instead opt for using \u201cbare IRIs\u201d when editing the ontology, everywhere you need a reference to a foreign term. Then, when submitting your pull request, label it with the tag update-imports-required to ask that a member of the tech support group refresh the imports before the pull request can be merged. People reviewing pull requests must 1) make sure that if a pull request is referencing bare IRIs, the request is tagged with update-imports-required (adding the label themselves if needed); and 2) make sure that imports have indeed been updated (either by the author of the pull request, or by someone from the tech support group if requested) before allowing the request to be merged.","title":"Pull requests that require imports to be refreshed"},{"location":"editing_guidelines/","text":"Guidelines for CL editors \u00b6 Naming terms \u00b6 General rules \u00b6 All labels should be singular nouns. Words should not be capitalized, unless they are proper names or are capitalized as a standard (e.g., \u201cPeyer's\u201d and \u201cB\u201d in \u201cPeyer's patch B cell\u201d). Avoid special characters: use only alphanumeric characters, space, dash ( - ), slash ( / ), and apostrophe ( ' ). Advice on writing labels \u00b6 Bear in mind that users will often encounter terms in isolation. Sufficiently descriptive labels are therefore recommended, especially where there is obvious potential for confusion. For example, the label 'peripheral nervous system neuron' is preferred over 'peripheral neuron' as the former has more clarity. For classes that refer to cell types in specific anatomical structures, the label may have the adjectival or noun form of the anatomical structure, for example, 'hepatic oval stem cell' or 'liver dendritic cell' (\"hepatic\" vs. \"liver\"). To determine which form of the anatomical term to use, it is recommended for the editor to search PubMed/review literature to determine common usage. If there is no clear preference, the editor can defer to use the adjectival form and add the noun form as an exact synonym. Try to maintain consistent patterns of naming where possible. However, it may make sense to override this in order to conform to common usage. Defining terms \u00b6 This section is about the textual definition of terms. Logically consistent classification is important, but an ontology is only useful (and maintainable) if all humans that interact with it (users, curators and editors) can quickly find the terms they need and understand what they refer to. This requires clear, unambiguous, human-readable definitions. When crafting a definition, editors should aim for a reasonably succinct statement about the class allowing curators and users to easily distinguish it from other, similar classes, and which captures key points of interest about that class. It should capture assertions made in the formal part of the definition (the relationships) as closely as possible without becoming stilted and difficult to read. The basic structure of a definition should be as follows: A genus that diff1 and diff2 . It also diff3 and gloss ... where genus is a general classification and diff1 , diff2 , etc. are the differentia , which state what differentiates this class from others that share the same general classification. The gloss, when present, gives some key points of interest about the class. The first sentence of the definition should refer to the definiendum in singular form. The rest of the definition may then invoke the plural form. Contents of definitions \u00b6 It is difficult to specify, a priori , which assertions should be included in a textual definition. However there are some general guidelines. DO make sure your definition is consistent with the definition of the superclass(es). DO make sure your definition includes the information that is recorded in all the direct formal relationships to the class. AVOID assertions about structures or cell types that are not part of the cell type being described, except when they pertain to some direct relationships with the cell type being described. AVOID including details that could better be included in the definition of subtypes of the cell type being described. LIMIT information that applies to only some members of the class. This should only be used sparingly; when used, it should be made clear that it does not apply to all members of the class. AVOID using gene expression as the differentium. AVOID extensive repetition of assertions made in superclass definitions, unless these assertions are used to provide direct evidence for class membership. DO NOT add informations about what happens in mutant or pathological states backgrounds or other kinds of non-control conditions. DO NOT include reasons for believing the assertions to be true. These should be recorded in comments. DO NOT raise questions in the definition. The definition should have the sense of being definitive, which is undermined if we show doubt. Any doubt should be recorded in comments instead. Comments \u00b6 Comments should be used for: Providing evidence. In some cases it is useful to know the type of evidence for an assertion. This should not be recorded in the definition, but can be recorded in a comment. Disambiguation. Sometimes a single term is used in the literature with multiple meanings. In such cases, a comment should be added outlining these different uses and how they relate to the definition set in the ontology. Reporting editorial decisions (or decisions in waiting) about the term. This includes providing a reason for obsoleting a term, or letting users and curators know that the term may be merged or split in the future, e.g., when enough evidence for the merge/split will be available. Try to be consistent in how you phrase the various types of comments. For example: when giving a reason for obsoletion, use \u201cObsoleted as ...\u201d; when indicating a potential future merge, use \u201cPossible equivalence with {other term} ...\u201d. Synonyms \u00b6 Extensive addition of synonyms helps \u201cfindability\u201d of terms when search. Synonyms can and should be added liberally. Of note, the intention of the ontology is not meant to record how a synonym is used in all specific sources in which it appears. Rather an editor, after doing due diligence in researching the terms/synonyms, must determine how a term is used at the present moment in the scientific community. Guidelines on the type of synonyms: DO use has_exact_synonym only when the label and the synonym can be used interchangeably without dispute and refer to the same concept. Example: the terms \u201cleukocyte\u201d, \u201cleucocyte\u201d (spelling variation\u201d) and \u201cwhite blood cell\u201d (layman\u2019s term) all refer to the exact same concept (a specific cell type) and would be considered exact synonyms. Terms that may refer to more than one concept, especially within the biomedical domain, should NOT be annotated as exact synonyms, including abbreviations. A synonym that is an abbreviation should be annotated using has_related_synonym and with property type \u201cabbreviation\u201d (technically: the synonym annotation assertion axiom should itself be annotated with a http://www.geneontology.org/formats/oboInOwl#hasSynonymType property with value http://purl.obolibrary.org/obo/OMO_0003000 ). Example: \u201cWBC\u201d can stand for \u201cwhite blood cell\u201d and refer to \u201cleukocyte\u201d, but within the biomedical domain it can also represent \u201cwhite blood cell count\u201d or, perhaps less frequently, \u201cwhole-body counting\u201d, two distinct concepts with separate OBO ontology terms. DO check that exact synonyms are unique across the ontology. In other words, if class A has synonym \u201cX\u201d, \u201cX\u201d should NOT be an exact synonym for any other CL term. DO be mindful of the \u201cdirectionality\u201d of the narrow and broad types of synonyms. They qualify the synonym , not the original term. Example: asserting that \u201cperipheral blood mononuclear cell\u201d is a narrow synonym of \u201cmononuclear cell\u201d means that \u201cperipheral blood mononuclear cell\u201c refers to a narrower (more specific) concept than \u201cmononuclear cell\u201d, not the other way around. DO use has_related_synonym where the overlap between the synonym and the term label may be uncler, disputable or not true in all scenarios or contexts, but do want the term to be findable when using the synonym as a search string. This includes abbreviations, which should be annotated as related synonyms with synonym type \u201cabbreviation\u201d (see point 2 above). If a synonym includes a mix of abbreviations and words, DO use has_related_synonym EXCEPT when there is enough context within the synonym itself to make it clear that the synonym refers only to the concept being annotated. Example: \u201clung TRM CD8-positive, CD103-positive cell\u201d should be an exact synonym of \u201clung resident memory CD8-positive, CD103-positive, alpha-beta T cell\u201d, even though \u201cTRM\u201d (in this case) is an abbreviation for \u201ctissue resident T cell\u201d. Note that without this context \u201cTRM\u201d should NOT be considered an exact synonym for \u201ctissue resident T cell\u201d as \u201cTRM\u201d could also mean \u201ctreatment-related mortality\u201d, another OBO ontology concept. Compare the previous example to \u201cIMB cell\u201d, which should be a related synonym of \u201cinvaginating midget bipolar cell\u201d, as there is not enough context to confidently infer what \"IMB\" stands for. Considerations on style \u00b6 The following considerations apply both to all human-readable fields (names, textual definitions, comments, synonyms). United States (US) English or British English? Where there are differences in the accepted spelling between British and US English, use the US form. British English variants of the labels may be added as synonyms (e.g., a term labelled \u201cepithelial cell of esophagus\u201d may have \u201cepithelial cell of oesophagus\u201d as an exact synonym). Use of jargon. The aim of the ontology is to provide useable descriptions and links to the reader. Consequently, try to avoid obscure jargon or pretentious Latin/Greek, especially where widely understood, plain alternatives exist. Where it will aid searching, such terms may be added to synonyms and/or as asides in the \u201cgloss\u201d part of definitions. That or which? These are so interchangeable that there isn\u2019t really a rule anymore. But as a guideline, use \u201cwhich\u201d after a comma (e.g., \u201cthis study, which cost $10,000, was a success\u201d), and \u201cthat\u201d when no comma is used (e.g., \u201cthe study that cost $10,000 was a success\u201d). Use of hyphens. Yes to those that help clarity (e.g., \u201cposterior-most\u201d) and those that are regularly used/accepted in the literature. Generally, no hyphens after prefixes such as \u201csub\u201d, \u201cmid\u201d, \u201csemi\u201d, \u201chemi\u201d, etc. (e.g., \u201chemidesmosome\u201d instead of \u201chemi-desmosome\u201d), unless it helps with clarity (e.g., \u201cmulti-innervated\u201d). No hyphens for composed location adjectives (e.g., \u201cposteroanterior\u201d), unless there are more than two compounds (e.g., \u201cventro-posterolateral\u201d). Abbreviations. Avoid abbreviations, contractions, and symbols born out of laziness, such as & , + , or vs for \u201cversus\u201d. Avoid abbreviations unless they are self-explanatory, commonly understood, or they really do help to reduce the amount of typing enough to enhance readability. Use full chemical element names, not symbols (e.g., \u201chydrogen\u201d instead of \u201cH+\u201d, \u201ccopper\u201d instead of \u201cCu\u201d, etc.). For biomolecules, spell out the term wherever practical (e.g., \u201cfibroblast growth factor\u201d instead of \u201cFGF\u201d). Abbreviations are acceptable in synonyms, in cases where the abbreviation is the synonym. Cross-references to the literature \u00b6 Assertions in textual definitions, evidence provided in comments, and synonyms should be backed up by citing the appropriate literature. Citations are made by cross-references, that is by adding http://www.geneontology.org/formats/oboInOwl#hasDbXref annotations to the definition, comment, and synonym annotations. Add one such annotation per reference, using the CURIE syntax with well-known prefixes: PMID:1234567 for a PubMed identifier; doi:xx.yyyy/... for a DOI; ISBN:... for a ISBN. If the main source for an assertion is a term in another ontology, the short identifier for that term may be used as a cross-reference. For example, WBbt:0006799 to cross-reference a term in the C. elegans Gross Anatomy Ontology. If using a MeSH (Medical Subject Heading) term as a cross-reference, add the database_cross_reference annotation using the MeSH Unique ID, NOT MeSH Tree Number. For example, a database_cross_reference can be MESH:D004759, NOT MESH:A03.492.766.440.250. ORCID identifiers may also be used when the only available source for an assertion is an individual researcher. However, this should be AVOIDED. Technical details of adding a cross-reference using Prot\u00e9g\u00e9 : For CURIEs, ORCIDs: In the \"Create Annotation\" window, select the annotation property database_cross_reference . For adding URLs to text definitions or synonyms: In the \"Create Annotation\" window, select the annotation property database_cross_reference . For adding URLs to axioms that are NOT text definitions or synonyms: In the \"Create Annotation\" window, select the annotation property source . For CURIEs: Enter the CURIE, using the bioregistry OBO context prefix ( link to prefixmap ), as a Value on the \"Literal\" tab. Leave Datatype empty. In cases where more than one CURIE is available for a resource, either is acceptable, but using the more semantically specific identifier is recommended. For example, when both a PMID and a doi are available for a resource, using the PMID is recommended since it indicates the cross-reference points to a paper, as opposed to a doi which could point to any digital object. For ORCIDs: Enter the ORCID as an IRI in the IRI field on the \"IRI Editor\" tab, for example https://orcid.org/0000-0002-7356-1779 . For URLs: Enter the URL as a literal string with Datatype xsd:anyURI selected. To restate, in all cases above except ORCIDs, the values are entered as literal strings. An ORCID MUST BE entered as an IRI. Term contributors \u00b6 When adding an ORCID to identify a term contributor, in the \"Create Annotation\" window, select the annotation property dcterms:contributor . The ORCID is still entered as an IRI on the \u201cIRI Editor\u201d tab. Formal definitions \u00b6 The formal definition of a class is made up of all the logical axioms about the class (as opposed to the annotation assertion axioms). This includes classification assertions, relationship assertions, equivalence assertions, and disjointness assertions. Note: In OWL formalism, both classification and relationship assertions are represented using SubClassOf axioms. However, in this document, we make a strict distinction between a classification (where a class is a subclass of a named class ), and a relationship (where a class is a subclass of an anonymous class expression ). For example, in the formal definition of CL:0000392 (\u201ccrystal cell\u201d): Class: 'crystal cell' SubClassOf: 'hemocyte' SubClassOf: 'develops from' some 'procrystal cell' The first SubClassOf axiom denotes an actual classification, whereas the second denotes a relationship. Asserting classification \u00b6 In order to keep the ontology maintainable, asserted classifications should be limited where possible. Ideally, all terms would have only a single asserted parent (also known as \u201csuperclass\u201d). Given the presence of suitable logically defined classes (see below) and sufficient relationships for the term you are making, additional classification can be inferred automatically by reasoning. However, as we are limited in what types of classification we are able to infer, you may need to assert multiple parents. Two asserted classifications are plainly acceptable. If you must assert three or more classifications, then you should make a note with a suggestion for which of the asserted classifications are good candidates for formalisation and inferred classification. Logically defined classes \u00b6 Logically defined classes are terms that have formal definitions that specify complete necessary and sufficient conditions for membership of the class. They can be used by a reasoner to auto-classify the ontology by searching for terms that fulfill these conditions. Logically defined classes are variously referred to as \u201ccross-products\u201d (XPs), \u201cgenus and differentia definitions\u201d, \u201cequivalent classes\u201d, or \u201cintersections\u201d. In OWL formalism, they are represented using EquivalentClasses axioms. Generally, logical definitions follow the structure: \u201cAny X that REL some Y [and REL some Z ...]\u201d, where X is the genus and each following clause (\u201cthat REL some Y \u201d) is a differentium. Strictly speaking, logical definitions can be arbitrarily complex. It is recommended, however, to stick as much as possible to \u201csimple\u201d logical definitions following the structure above. More complex definitions are harder to understand, and may involve OWL constructs that are legal but not fully supported by the available reasoners. Good candidates for logically defined classes include classes whose only differentium is: what the cell is part of; what it innervates (or, if more precisely known, what its axon(s) or dendrite(s) innervate); its function. Care must be taken not to use logically defined classes too liberally. You should be satisfied that all automatic classifications that result would make sense (or at least be justifiable) to a biologist and would cover most cases of usages with as few edge cases as possible. If you are unsure whether a characteristic of a cell should be expressed as a relationship or as a differentium in a logically defined class: use a relationship. The textual definition of a logically defined class should be a plain English equivalent of the logical definition. However, in some cases it can be useful to add more details in the \u201cgloss\u201d part when relevant. Asserting relationships \u00b6 Please refer to the relations guide for detailled guidelines about which relations to use for most cases. Design Pattern Usage with DOSDP \u00b6 The cell ontology contains a large number of terms, classifications, and relationships that are constantly expanding. Manually maintaining all these elements would be a daunting task, so a substantial portion of the maintenance is automated. This automation heavily relies on the systematic use of design patterns. CL uses Dead simple OWL design patterns (DOSDP, Osumi-Sutherland et al. , 2017 ) to document simple patterns, as they require minimal programming expertise, and once implemented, it is easy to edit. All patterns are stored in /src/patterns/dosdp-patterns , while the editable tables are located in /src/patterns/data/default . Cycling cell states \u00b6 Pattern name: cyclingCellStates.yaml Cell cycling is a fundamental biological mechanism that brings a cell to divide and duplicate into two daughter cells. This process is carried out by a large number of cells, and cells undergoing cell cycling have a distinct transcriptional profile compared to non cycling cells. Cycling cells need to be annotated and described by CL terms as these cells have been identified in a multitude of single-cell transcriptional datasets. The terms created with this pattern are labeled as 'cycling X' , being X the parent term, and have an exact synonym 'proliferating X' . These cell terms can be logically defined as a 'cell' and 'participates in' some 'cell cycle process' and 'has quality' some 'active' . Ex: 'cycling B cell' SubClassOf 'B cell and 'participates in' some 'cell cycle process' and 'has quality' some 'active'","title":"Guidelines for CL editors"},{"location":"editing_guidelines/#guidelines-for-cl-editors","text":"","title":"Guidelines for CL editors"},{"location":"editing_guidelines/#naming-terms","text":"","title":"Naming terms"},{"location":"editing_guidelines/#general-rules","text":"All labels should be singular nouns. Words should not be capitalized, unless they are proper names or are capitalized as a standard (e.g., \u201cPeyer's\u201d and \u201cB\u201d in \u201cPeyer's patch B cell\u201d). Avoid special characters: use only alphanumeric characters, space, dash ( - ), slash ( / ), and apostrophe ( ' ).","title":"General rules"},{"location":"editing_guidelines/#advice-on-writing-labels","text":"Bear in mind that users will often encounter terms in isolation. Sufficiently descriptive labels are therefore recommended, especially where there is obvious potential for confusion. For example, the label 'peripheral nervous system neuron' is preferred over 'peripheral neuron' as the former has more clarity. For classes that refer to cell types in specific anatomical structures, the label may have the adjectival or noun form of the anatomical structure, for example, 'hepatic oval stem cell' or 'liver dendritic cell' (\"hepatic\" vs. \"liver\"). To determine which form of the anatomical term to use, it is recommended for the editor to search PubMed/review literature to determine common usage. If there is no clear preference, the editor can defer to use the adjectival form and add the noun form as an exact synonym. Try to maintain consistent patterns of naming where possible. However, it may make sense to override this in order to conform to common usage.","title":"Advice on writing labels"},{"location":"editing_guidelines/#defining-terms","text":"This section is about the textual definition of terms. Logically consistent classification is important, but an ontology is only useful (and maintainable) if all humans that interact with it (users, curators and editors) can quickly find the terms they need and understand what they refer to. This requires clear, unambiguous, human-readable definitions. When crafting a definition, editors should aim for a reasonably succinct statement about the class allowing curators and users to easily distinguish it from other, similar classes, and which captures key points of interest about that class. It should capture assertions made in the formal part of the definition (the relationships) as closely as possible without becoming stilted and difficult to read. The basic structure of a definition should be as follows: A genus that diff1 and diff2 . It also diff3 and gloss ... where genus is a general classification and diff1 , diff2 , etc. are the differentia , which state what differentiates this class from others that share the same general classification. The gloss, when present, gives some key points of interest about the class. The first sentence of the definition should refer to the definiendum in singular form. The rest of the definition may then invoke the plural form.","title":"Defining terms"},{"location":"editing_guidelines/#contents-of-definitions","text":"It is difficult to specify, a priori , which assertions should be included in a textual definition. However there are some general guidelines. DO make sure your definition is consistent with the definition of the superclass(es). DO make sure your definition includes the information that is recorded in all the direct formal relationships to the class. AVOID assertions about structures or cell types that are not part of the cell type being described, except when they pertain to some direct relationships with the cell type being described. AVOID including details that could better be included in the definition of subtypes of the cell type being described. LIMIT information that applies to only some members of the class. This should only be used sparingly; when used, it should be made clear that it does not apply to all members of the class. AVOID using gene expression as the differentium. AVOID extensive repetition of assertions made in superclass definitions, unless these assertions are used to provide direct evidence for class membership. DO NOT add informations about what happens in mutant or pathological states backgrounds or other kinds of non-control conditions. DO NOT include reasons for believing the assertions to be true. These should be recorded in comments. DO NOT raise questions in the definition. The definition should have the sense of being definitive, which is undermined if we show doubt. Any doubt should be recorded in comments instead.","title":"Contents of definitions"},{"location":"editing_guidelines/#comments","text":"Comments should be used for: Providing evidence. In some cases it is useful to know the type of evidence for an assertion. This should not be recorded in the definition, but can be recorded in a comment. Disambiguation. Sometimes a single term is used in the literature with multiple meanings. In such cases, a comment should be added outlining these different uses and how they relate to the definition set in the ontology. Reporting editorial decisions (or decisions in waiting) about the term. This includes providing a reason for obsoleting a term, or letting users and curators know that the term may be merged or split in the future, e.g., when enough evidence for the merge/split will be available. Try to be consistent in how you phrase the various types of comments. For example: when giving a reason for obsoletion, use \u201cObsoleted as ...\u201d; when indicating a potential future merge, use \u201cPossible equivalence with {other term} ...\u201d.","title":"Comments"},{"location":"editing_guidelines/#synonyms","text":"Extensive addition of synonyms helps \u201cfindability\u201d of terms when search. Synonyms can and should be added liberally. Of note, the intention of the ontology is not meant to record how a synonym is used in all specific sources in which it appears. Rather an editor, after doing due diligence in researching the terms/synonyms, must determine how a term is used at the present moment in the scientific community. Guidelines on the type of synonyms: DO use has_exact_synonym only when the label and the synonym can be used interchangeably without dispute and refer to the same concept. Example: the terms \u201cleukocyte\u201d, \u201cleucocyte\u201d (spelling variation\u201d) and \u201cwhite blood cell\u201d (layman\u2019s term) all refer to the exact same concept (a specific cell type) and would be considered exact synonyms. Terms that may refer to more than one concept, especially within the biomedical domain, should NOT be annotated as exact synonyms, including abbreviations. A synonym that is an abbreviation should be annotated using has_related_synonym and with property type \u201cabbreviation\u201d (technically: the synonym annotation assertion axiom should itself be annotated with a http://www.geneontology.org/formats/oboInOwl#hasSynonymType property with value http://purl.obolibrary.org/obo/OMO_0003000 ). Example: \u201cWBC\u201d can stand for \u201cwhite blood cell\u201d and refer to \u201cleukocyte\u201d, but within the biomedical domain it can also represent \u201cwhite blood cell count\u201d or, perhaps less frequently, \u201cwhole-body counting\u201d, two distinct concepts with separate OBO ontology terms. DO check that exact synonyms are unique across the ontology. In other words, if class A has synonym \u201cX\u201d, \u201cX\u201d should NOT be an exact synonym for any other CL term. DO be mindful of the \u201cdirectionality\u201d of the narrow and broad types of synonyms. They qualify the synonym , not the original term. Example: asserting that \u201cperipheral blood mononuclear cell\u201d is a narrow synonym of \u201cmononuclear cell\u201d means that \u201cperipheral blood mononuclear cell\u201c refers to a narrower (more specific) concept than \u201cmononuclear cell\u201d, not the other way around. DO use has_related_synonym where the overlap between the synonym and the term label may be uncler, disputable or not true in all scenarios or contexts, but do want the term to be findable when using the synonym as a search string. This includes abbreviations, which should be annotated as related synonyms with synonym type \u201cabbreviation\u201d (see point 2 above). If a synonym includes a mix of abbreviations and words, DO use has_related_synonym EXCEPT when there is enough context within the synonym itself to make it clear that the synonym refers only to the concept being annotated. Example: \u201clung TRM CD8-positive, CD103-positive cell\u201d should be an exact synonym of \u201clung resident memory CD8-positive, CD103-positive, alpha-beta T cell\u201d, even though \u201cTRM\u201d (in this case) is an abbreviation for \u201ctissue resident T cell\u201d. Note that without this context \u201cTRM\u201d should NOT be considered an exact synonym for \u201ctissue resident T cell\u201d as \u201cTRM\u201d could also mean \u201ctreatment-related mortality\u201d, another OBO ontology concept. Compare the previous example to \u201cIMB cell\u201d, which should be a related synonym of \u201cinvaginating midget bipolar cell\u201d, as there is not enough context to confidently infer what \"IMB\" stands for.","title":"Synonyms"},{"location":"editing_guidelines/#considerations-on-style","text":"The following considerations apply both to all human-readable fields (names, textual definitions, comments, synonyms). United States (US) English or British English? Where there are differences in the accepted spelling between British and US English, use the US form. British English variants of the labels may be added as synonyms (e.g., a term labelled \u201cepithelial cell of esophagus\u201d may have \u201cepithelial cell of oesophagus\u201d as an exact synonym). Use of jargon. The aim of the ontology is to provide useable descriptions and links to the reader. Consequently, try to avoid obscure jargon or pretentious Latin/Greek, especially where widely understood, plain alternatives exist. Where it will aid searching, such terms may be added to synonyms and/or as asides in the \u201cgloss\u201d part of definitions. That or which? These are so interchangeable that there isn\u2019t really a rule anymore. But as a guideline, use \u201cwhich\u201d after a comma (e.g., \u201cthis study, which cost $10,000, was a success\u201d), and \u201cthat\u201d when no comma is used (e.g., \u201cthe study that cost $10,000 was a success\u201d). Use of hyphens. Yes to those that help clarity (e.g., \u201cposterior-most\u201d) and those that are regularly used/accepted in the literature. Generally, no hyphens after prefixes such as \u201csub\u201d, \u201cmid\u201d, \u201csemi\u201d, \u201chemi\u201d, etc. (e.g., \u201chemidesmosome\u201d instead of \u201chemi-desmosome\u201d), unless it helps with clarity (e.g., \u201cmulti-innervated\u201d). No hyphens for composed location adjectives (e.g., \u201cposteroanterior\u201d), unless there are more than two compounds (e.g., \u201cventro-posterolateral\u201d). Abbreviations. Avoid abbreviations, contractions, and symbols born out of laziness, such as & , + , or vs for \u201cversus\u201d. Avoid abbreviations unless they are self-explanatory, commonly understood, or they really do help to reduce the amount of typing enough to enhance readability. Use full chemical element names, not symbols (e.g., \u201chydrogen\u201d instead of \u201cH+\u201d, \u201ccopper\u201d instead of \u201cCu\u201d, etc.). For biomolecules, spell out the term wherever practical (e.g., \u201cfibroblast growth factor\u201d instead of \u201cFGF\u201d). Abbreviations are acceptable in synonyms, in cases where the abbreviation is the synonym.","title":"Considerations on style"},{"location":"editing_guidelines/#cross-references-to-the-literature","text":"Assertions in textual definitions, evidence provided in comments, and synonyms should be backed up by citing the appropriate literature. Citations are made by cross-references, that is by adding http://www.geneontology.org/formats/oboInOwl#hasDbXref annotations to the definition, comment, and synonym annotations. Add one such annotation per reference, using the CURIE syntax with well-known prefixes: PMID:1234567 for a PubMed identifier; doi:xx.yyyy/... for a DOI; ISBN:... for a ISBN. If the main source for an assertion is a term in another ontology, the short identifier for that term may be used as a cross-reference. For example, WBbt:0006799 to cross-reference a term in the C. elegans Gross Anatomy Ontology. If using a MeSH (Medical Subject Heading) term as a cross-reference, add the database_cross_reference annotation using the MeSH Unique ID, NOT MeSH Tree Number. For example, a database_cross_reference can be MESH:D004759, NOT MESH:A03.492.766.440.250. ORCID identifiers may also be used when the only available source for an assertion is an individual researcher. However, this should be AVOIDED. Technical details of adding a cross-reference using Prot\u00e9g\u00e9 : For CURIEs, ORCIDs: In the \"Create Annotation\" window, select the annotation property database_cross_reference . For adding URLs to text definitions or synonyms: In the \"Create Annotation\" window, select the annotation property database_cross_reference . For adding URLs to axioms that are NOT text definitions or synonyms: In the \"Create Annotation\" window, select the annotation property source . For CURIEs: Enter the CURIE, using the bioregistry OBO context prefix ( link to prefixmap ), as a Value on the \"Literal\" tab. Leave Datatype empty. In cases where more than one CURIE is available for a resource, either is acceptable, but using the more semantically specific identifier is recommended. For example, when both a PMID and a doi are available for a resource, using the PMID is recommended since it indicates the cross-reference points to a paper, as opposed to a doi which could point to any digital object. For ORCIDs: Enter the ORCID as an IRI in the IRI field on the \"IRI Editor\" tab, for example https://orcid.org/0000-0002-7356-1779 . For URLs: Enter the URL as a literal string with Datatype xsd:anyURI selected. To restate, in all cases above except ORCIDs, the values are entered as literal strings. An ORCID MUST BE entered as an IRI.","title":"Cross-references to the literature"},{"location":"editing_guidelines/#term-contributors","text":"When adding an ORCID to identify a term contributor, in the \"Create Annotation\" window, select the annotation property dcterms:contributor . The ORCID is still entered as an IRI on the \u201cIRI Editor\u201d tab.","title":"Term contributors"},{"location":"editing_guidelines/#formal-definitions","text":"The formal definition of a class is made up of all the logical axioms about the class (as opposed to the annotation assertion axioms). This includes classification assertions, relationship assertions, equivalence assertions, and disjointness assertions. Note: In OWL formalism, both classification and relationship assertions are represented using SubClassOf axioms. However, in this document, we make a strict distinction between a classification (where a class is a subclass of a named class ), and a relationship (where a class is a subclass of an anonymous class expression ). For example, in the formal definition of CL:0000392 (\u201ccrystal cell\u201d): Class: 'crystal cell' SubClassOf: 'hemocyte' SubClassOf: 'develops from' some 'procrystal cell' The first SubClassOf axiom denotes an actual classification, whereas the second denotes a relationship.","title":"Formal definitions"},{"location":"editing_guidelines/#asserting-classification","text":"In order to keep the ontology maintainable, asserted classifications should be limited where possible. Ideally, all terms would have only a single asserted parent (also known as \u201csuperclass\u201d). Given the presence of suitable logically defined classes (see below) and sufficient relationships for the term you are making, additional classification can be inferred automatically by reasoning. However, as we are limited in what types of classification we are able to infer, you may need to assert multiple parents. Two asserted classifications are plainly acceptable. If you must assert three or more classifications, then you should make a note with a suggestion for which of the asserted classifications are good candidates for formalisation and inferred classification.","title":"Asserting classification"},{"location":"editing_guidelines/#logically-defined-classes","text":"Logically defined classes are terms that have formal definitions that specify complete necessary and sufficient conditions for membership of the class. They can be used by a reasoner to auto-classify the ontology by searching for terms that fulfill these conditions. Logically defined classes are variously referred to as \u201ccross-products\u201d (XPs), \u201cgenus and differentia definitions\u201d, \u201cequivalent classes\u201d, or \u201cintersections\u201d. In OWL formalism, they are represented using EquivalentClasses axioms. Generally, logical definitions follow the structure: \u201cAny X that REL some Y [and REL some Z ...]\u201d, where X is the genus and each following clause (\u201cthat REL some Y \u201d) is a differentium. Strictly speaking, logical definitions can be arbitrarily complex. It is recommended, however, to stick as much as possible to \u201csimple\u201d logical definitions following the structure above. More complex definitions are harder to understand, and may involve OWL constructs that are legal but not fully supported by the available reasoners. Good candidates for logically defined classes include classes whose only differentium is: what the cell is part of; what it innervates (or, if more precisely known, what its axon(s) or dendrite(s) innervate); its function. Care must be taken not to use logically defined classes too liberally. You should be satisfied that all automatic classifications that result would make sense (or at least be justifiable) to a biologist and would cover most cases of usages with as few edge cases as possible. If you are unsure whether a characteristic of a cell should be expressed as a relationship or as a differentium in a logically defined class: use a relationship. The textual definition of a logically defined class should be a plain English equivalent of the logical definition. However, in some cases it can be useful to add more details in the \u201cgloss\u201d part when relevant.","title":"Logically defined classes"},{"location":"editing_guidelines/#asserting-relationships","text":"Please refer to the relations guide for detailled guidelines about which relations to use for most cases.","title":"Asserting relationships"},{"location":"editing_guidelines/#design-pattern-usage-with-dosdp","text":"The cell ontology contains a large number of terms, classifications, and relationships that are constantly expanding. Manually maintaining all these elements would be a daunting task, so a substantial portion of the maintenance is automated. This automation heavily relies on the systematic use of design patterns. CL uses Dead simple OWL design patterns (DOSDP, Osumi-Sutherland et al. , 2017 ) to document simple patterns, as they require minimal programming expertise, and once implemented, it is easy to edit. All patterns are stored in /src/patterns/dosdp-patterns , while the editable tables are located in /src/patterns/data/default .","title":"Design Pattern Usage with DOSDP"},{"location":"editing_guidelines/#cycling-cell-states","text":"Pattern name: cyclingCellStates.yaml Cell cycling is a fundamental biological mechanism that brings a cell to divide and duplicate into two daughter cells. This process is carried out by a large number of cells, and cells undergoing cell cycling have a distinct transcriptional profile compared to non cycling cells. Cycling cells need to be annotated and described by CL terms as these cells have been identified in a multitude of single-cell transcriptional datasets. The terms created with this pattern are labeled as 'cycling X' , being X the parent term, and have an exact synonym 'proliferating X' . These cell terms can be logically defined as a 'cell' and 'participates in' some 'cell cycle process' and 'has quality' some 'active' . Ex: 'cycling B cell' SubClassOf 'B cell and 'participates in' some 'cell cycle process' and 'has quality' some 'active'","title":"Cycling cell states"},{"location":"history/","text":"A brief history of CL \u00b6 The Cell Ontology (CL) represents canonical, natural biological cell types in a variety of animal species, with a focus on vertebrates. It works in concert with the Uberon anatomy ontology, and integrates seamlessly with the Gene Ontology (GO). It is part of the Open Biological and Biomedical Ontology (OBO) Foundry. As of April 2022, CL consists of over 2400 classes, and is used by key projects that involve single-cell transcriptomics, such as HuBMAP (HuBMAP Consortium 2019), the Human Cell Atlas (Regev et al. 2017), the EBI Single Cell Expression Atlas (Papatheodorou et al. 2020), and the Brain Data Standards Ontology (Tan et al. 2021).","title":"History"},{"location":"history/#a-brief-history-of-cl","text":"The Cell Ontology (CL) represents canonical, natural biological cell types in a variety of animal species, with a focus on vertebrates. It works in concert with the Uberon anatomy ontology, and integrates seamlessly with the Gene Ontology (GO). It is part of the Open Biological and Biomedical Ontology (OBO) Foundry. As of April 2022, CL consists of over 2400 classes, and is used by key projects that involve single-cell transcriptomics, such as HuBMAP (HuBMAP Consortium 2019), the Human Cell Atlas (Regev et al. 2017), the EBI Single Cell Expression Atlas (Papatheodorou et al. 2020), and the Brain Data Standards Ontology (Tan et al. 2021).","title":"A brief history of CL"},{"location":"merging-terms/","text":"Merging Terms in CL \u00b6 How to merge terms \u00b6 For general instructions on how to merge terms, please see this How-to Guide In addition to the above, please add the annotation has_alternative_id on the winning term with the ID of the losing term. Considerations on which should be the winning term \u00b6 Check Usage by GO - This can be done by using AmiGO 2 Check Usage by other ontologies - This can be done by using Ontobee Check Usage within CL (you can do this in Protege with the usage tab) - this should be lower priority as you can easily change this while obsoleting the \"losing\" term","title":"Merging Terms in CL"},{"location":"merging-terms/#merging-terms-in-cl","text":"","title":"Merging Terms in CL"},{"location":"merging-terms/#how-to-merge-terms","text":"For general instructions on how to merge terms, please see this How-to Guide In addition to the above, please add the annotation has_alternative_id on the winning term with the ID of the losing term.","title":"How to merge terms"},{"location":"merging-terms/#considerations-on-which-should-be-the-winning-term","text":"Check Usage by GO - This can be done by using AmiGO 2 Check Usage by other ontologies - This can be done by using Ontobee Check Usage within CL (you can do this in Protege with the usage tab) - this should be lower priority as you can easily change this while obsoleting the \"losing\" term","title":"Considerations on which should be the winning term"},{"location":"presentations/","text":"Presentations \u00b6 Cell Ontology (Genentech presentation) by Nicole Vasilevsky. Presented on 2021-02-09.","title":"CL presentations"},{"location":"presentations/#presentations","text":"Cell Ontology (Genentech presentation) by Nicole Vasilevsky. Presented on 2021-02-09.","title":"Presentations"},{"location":"relations_guide/","text":"Cell Ontology (CL) relations guide. \u00b6 Intro \u00b6 The aim of this document is to provide an accessible guide to how to use relations to record the properties that define cell types including location, lineage, function, morphology and marker genes. The term 'relations' here refers principally to OWL object properties, but also includes annoation properties used as shortcuts for more expressive logical axioms that can be programatically generated from them. Relations in this guide are grouped by general use case (e.g. recording location) and each is illustrated by an example e.g.- melanocyte subClassOf \u2018has part\u2019 some melanosome. This should be read as \u2018 all melanocytes have some type of melanosome as a part\u2019 as should all axioms of this form. The examples should all be correct, but may not reflect the full complexity of axioms in the ontology. Where no example is currently present in CL, examples are taken from the Drosophila Anatomy Ontology, which follows the same schema. Recording location \u00b6 Location of cell types is recorded by relating a cell type to a term in an anatomical ontology. For the Cell Ontology this means a term from Uberon. 'part of' \u00b6 Use part_of for cases where the location is a material anatomical structure (rather than a space, such as a sinus) and all of the cell is within the anatomical structure. \u2018 epithelial cell' subClassOf 'part of' some epithelium \u2018part of\u2019 is transitive, which means that it applies across chains of relationships. For example, \u2018ileal goblet cell\u2019 part_of some ileum ilium \u2018part of\u2019 some \u2018small intestine\u2019 \u2018small intestine\u2019 \u2018part of\u2019 some intestine => \u2019ileal goblet cell\u2019 \u2018part of\u2019 some \u2018small intestine\u2019 & \u2018ileal goblet cell\u2019 \u2018part of\u2019 some intestine located_in \u00b6 To record the location of a cell in an anatomical space (e.g., a sinus), 'located in' is used. For example: \u2018 lymph node marginal reticular cell \u2019 subClassOf 'located in' some 'subcapsular sinus of lymph node' overlaps \u00b6 'part of' applies in cases where an entire cell is within an anatomical structure, but some cells have parts in multiple anatomical structures. For example, many neurons span multiple regions of the central nervous system. The general relation for this is overlaps (has some part in). overlaps is not currently used directly in the cell ontology (time of writing 05/2023), but more specific relationships exist for recording the location of neurons and their parts. These are described in the next section. Recording the location of neurons \u00b6 has soma location \u00b6 When neurobiologists talk about the location of vertebrate neurons, they are typically referring to soma location. The importance of soma location to identify is underscored by how commonly cell types are named, in part, by soma location. We therefore have a dedicated relation for recording this: 'has soma location' . For example, anterior horn motor neuron has the following subclass axiom: 'has soma location' some 'ventral horn of spinal cord' axiomatization of \u2018has soma location\u2019 subPropertyOf : overlaps # if X has_soma_location some Y, then X overlaps some Y) domain : neuron # X has_soma_location some Y => X is inferred to be a subClassOf neuron property chain : has_soma_location o part_of --> has_soma_location # If x has soma location y and y is part_of z, then x has_soma_location_z Example of reasoning with the property chain: 'cortical interneuron' equivalentTo 'interneuron' that has_soma_location some 'cerebral cortex' 'rosehip neuron' subClassOf interneuron and has_soma_location some 'cortical layer 1' 'cortical layer 1' subClassOf part_of some 'cerebral cortex => 'rosehip neuron' subClassOf 'cortical interneuron' sends synaptic output to region \u00b6 A relationship between a neuron and a region, where the neuron has a functionally relevant number of output synapses in that region. ' adult basket subesophageal neuron ' SubClassOf sends synaptic output to region some inferior posterior slope receives synaptic input in region \u00b6 A relationship between a neuron and a region, where the neuron has a functionally relevant number of input synapses: e.g. ' adult basket subesophageal neuron ' SubClassOf \u2018 receives synaptic input in region \u2019 some \u2018 superior posterior slope \u2019 fasciculates_with \u00b6 Use this to record the tracts or nerves that a neuron\u2019s projections fasciculate with. e.g. \u2018Betz cell\u2019 subClasssOf \u2018fasciculates with\u2019 some \u2018corticospinal tract\u2019. subPropertyOf: overlaps domain: neuron range: neuron projection bundle Recording synaptic connectivity (neurons) \u00b6 To record neuron-to-neuron or motor neuron-to-target muscle connectivity, consider the following object properties. These properties should be used when connectivity is key to the definition, for example, in cases where a motor neuron type is defined by the type of muscle fiber on which it synapses. synapsed to \u00b6 For example, 'alpha motor neuron' SubClassOf synapsed to some 'extrafusal muscle fiber' synapsed by \u00b6 This is the the inverse of synapsed to For example, 'extrafusal muscle fiber' SubClassOf synapsed by some 'alpha motor neuron' Recording function \u00b6 Cellular function is recorded by linking GO biological process terms with the object properties 'capable of' and \u2018capable of part of\u2019 'capable of' \u00b6 Use this relation where the cell is capable of carrying out the entirety of the process For example, 'hilus cell of ovary' has the following subclass: 'capable of' some 'androgen secretion' Recording neurotransmitter for neurons \u00b6 To record which neurotransmitter a neuron releases, use a 'capable of' relation to link the neuron to a subclass of GO neurotransmitter secretion that references a neurotransmitter type. This should be sufficient for autoclassification. e.g. 'medium spiny neuron' 'capable of' some 'gamma-aminobutyric acid secretion, neurotransmission' \u2018capable of part of\u2019 \u00b6 Use this relationship where only part of the process occurs in the cell type. e.g. 'retinal bipolar neuron' 'capable of part of' some 'visual perception' Recording developmental lineage \u00b6 Developmental lineage is recorded between cell types with the object property develops from (a transitive property), or in the case where there are no intermediates between the cells, 'directly develops from' (a non-transitive subproperty of develops_from ) For example, 'leukocyte' subClassOf develops from some 'hematopoietic stem cell' Recording cell markers \u00b6 Only markers that are necessary to define a cell type should be recorded. cell surface (protein) markers \u00b6 The cell ontology has a set of terms for recording cell surface markers. The most commonly used relation for recording markers is 'has plasma membrane part' . This object property is used to record cell surface markers, especially in immune cells. There are also more specific properties, 'has low plasma membrane amount' and 'has high plasma membrane amount' , that can be used at an editor's discretion. In each case, a term from the PRotein Ontology (PRO) or a protein complex term from the Gene Ontology (GO) is used as the object of the relation. For example, 'alpha-beta T cell' has the following equivalence axiom: 'T cell' and 'has plasma membrane part' some 'alpha-beta T cell receptor complex' Absence of a marker can be recorded using lacks_plasma_membrane_part Warning - this is used in place of the more accurate OWL expression \"NOT has_part some X*** in order to keep within the EL profile of OWL. It's use with a general class as a target can potentially lead to reasoning errors. recording gene markers \u00b6 \u2018expresses\u2019 \u00b6 Use this to link a cell type to a gene or gene product that defines it: For example: 'lamp5 GABAergic cortical interneuron' EquivalentTo: interneuron and ('has soma location' some 'cerebral cortex') and ('capable of' some 'gamma-aminobutyric acid secretion, neurotransmission') and (expresses some 'lysosome-associated membrane glycoprotein 5') In FBbt, FlyBase Gene IDs are permitted here (using standard resolvable URL pattern). In CL currenly only PRO IDs are permitted. In PCL, a broader range of IDs have been used (depending on data sources used). Recording cell parts \u00b6 To record parts above the granularity of proteins and complexes, use a 'has part' relationship with an object from the Gene Ontology cellular_component branch. e.g. 'melanocyte' subClassOf 'has part' some 'melanosome' This GO term can be combined with a PATO quality term (e.g. for shape) where necessary, e.g. For example: 'mature basophil' subClassOf ('has part' some (nucleus and ('has characteristic' some lobed))) Recording general cellular characteristics \u00b6 The ontology PATO , has a rich set of terms that can be used to record the general characteristics of cells, such as their morphology. These are recorded using 'has characteristic' . In choosing PATO terms, avoid those referring to some change in characteristic (e.g,.\u2019 increased branchiness\u2019). The following list of examples is not exhaustive: Recording Morphology \u00b6 PATO has a set of general morphology terms which may be applicable to cells. For example, erythrocyte subClassOf 'has characteristic' some biconcave PATO also has a set of terms for specific cell morphologies (mostly neuronal), e.g. \u2018Betz cell\u2019 subClassOf \u2018has characteristic\u2019 some \u2018standard pyramidal morphology\u2019 Recording nuclear number \u00b6 To record the number of nuclei in a cell, use a PATO subclass under the term 'nucleate quality' with the 'has characteristic' relation. For example, platelet subClassOf ( 'has_characteristic' some anucleate ) Note - that PATO includes bridging axioms that infer part relationships based on these characteristics. e.g. cell and ('has characteristic' some multinucleate) SubClassOf 'has part' some nucleus Taxon constraints \u00b6 See https://oboacademy.github.io/obook/explanation/taxon-constraints-explainer/ .","title":"CL relations"},{"location":"relations_guide/#cell-ontology-cl-relations-guide","text":"","title":"Cell Ontology (CL) relations guide."},{"location":"relations_guide/#intro","text":"The aim of this document is to provide an accessible guide to how to use relations to record the properties that define cell types including location, lineage, function, morphology and marker genes. The term 'relations' here refers principally to OWL object properties, but also includes annoation properties used as shortcuts for more expressive logical axioms that can be programatically generated from them. Relations in this guide are grouped by general use case (e.g. recording location) and each is illustrated by an example e.g.- melanocyte subClassOf \u2018has part\u2019 some melanosome. This should be read as \u2018 all melanocytes have some type of melanosome as a part\u2019 as should all axioms of this form. The examples should all be correct, but may not reflect the full complexity of axioms in the ontology. Where no example is currently present in CL, examples are taken from the Drosophila Anatomy Ontology, which follows the same schema.","title":"Intro"},{"location":"relations_guide/#recording-location","text":"Location of cell types is recorded by relating a cell type to a term in an anatomical ontology. For the Cell Ontology this means a term from Uberon.","title":"Recording location"},{"location":"relations_guide/#part-of","text":"Use part_of for cases where the location is a material anatomical structure (rather than a space, such as a sinus) and all of the cell is within the anatomical structure. \u2018 epithelial cell' subClassOf 'part of' some epithelium \u2018part of\u2019 is transitive, which means that it applies across chains of relationships. For example, \u2018ileal goblet cell\u2019 part_of some ileum ilium \u2018part of\u2019 some \u2018small intestine\u2019 \u2018small intestine\u2019 \u2018part of\u2019 some intestine => \u2019ileal goblet cell\u2019 \u2018part of\u2019 some \u2018small intestine\u2019 & \u2018ileal goblet cell\u2019 \u2018part of\u2019 some intestine","title":"'part of'"},{"location":"relations_guide/#located_in","text":"To record the location of a cell in an anatomical space (e.g., a sinus), 'located in' is used. For example: \u2018 lymph node marginal reticular cell \u2019 subClassOf 'located in' some 'subcapsular sinus of lymph node'","title":"located_in"},{"location":"relations_guide/#overlaps","text":"'part of' applies in cases where an entire cell is within an anatomical structure, but some cells have parts in multiple anatomical structures. For example, many neurons span multiple regions of the central nervous system. The general relation for this is overlaps (has some part in). overlaps is not currently used directly in the cell ontology (time of writing 05/2023), but more specific relationships exist for recording the location of neurons and their parts. These are described in the next section.","title":"overlaps"},{"location":"relations_guide/#recording-the-location-of-neurons","text":"","title":"Recording the location of neurons"},{"location":"relations_guide/#has-soma-location","text":"When neurobiologists talk about the location of vertebrate neurons, they are typically referring to soma location. The importance of soma location to identify is underscored by how commonly cell types are named, in part, by soma location. We therefore have a dedicated relation for recording this: 'has soma location' . For example, anterior horn motor neuron has the following subclass axiom: 'has soma location' some 'ventral horn of spinal cord' axiomatization of \u2018has soma location\u2019 subPropertyOf : overlaps # if X has_soma_location some Y, then X overlaps some Y) domain : neuron # X has_soma_location some Y => X is inferred to be a subClassOf neuron property chain : has_soma_location o part_of --> has_soma_location # If x has soma location y and y is part_of z, then x has_soma_location_z Example of reasoning with the property chain: 'cortical interneuron' equivalentTo 'interneuron' that has_soma_location some 'cerebral cortex' 'rosehip neuron' subClassOf interneuron and has_soma_location some 'cortical layer 1' 'cortical layer 1' subClassOf part_of some 'cerebral cortex => 'rosehip neuron' subClassOf 'cortical interneuron'","title":"has soma location"},{"location":"relations_guide/#sends-synaptic-output-to-region","text":"A relationship between a neuron and a region, where the neuron has a functionally relevant number of output synapses in that region. ' adult basket subesophageal neuron ' SubClassOf sends synaptic output to region some inferior posterior slope","title":"sends synaptic output to region"},{"location":"relations_guide/#receives-synaptic-input-in-region","text":"A relationship between a neuron and a region, where the neuron has a functionally relevant number of input synapses: e.g. ' adult basket subesophageal neuron ' SubClassOf \u2018 receives synaptic input in region \u2019 some \u2018 superior posterior slope \u2019","title":"receives synaptic input in region"},{"location":"relations_guide/#fasciculates_with","text":"Use this to record the tracts or nerves that a neuron\u2019s projections fasciculate with. e.g. \u2018Betz cell\u2019 subClasssOf \u2018fasciculates with\u2019 some \u2018corticospinal tract\u2019. subPropertyOf: overlaps domain: neuron range: neuron projection bundle","title":"fasciculates_with"},{"location":"relations_guide/#recording-synaptic-connectivity-neurons","text":"To record neuron-to-neuron or motor neuron-to-target muscle connectivity, consider the following object properties. These properties should be used when connectivity is key to the definition, for example, in cases where a motor neuron type is defined by the type of muscle fiber on which it synapses.","title":"Recording synaptic connectivity (neurons)"},{"location":"relations_guide/#synapsed-to","text":"For example, 'alpha motor neuron' SubClassOf synapsed to some 'extrafusal muscle fiber'","title":"synapsed to"},{"location":"relations_guide/#synapsed-by","text":"This is the the inverse of synapsed to For example, 'extrafusal muscle fiber' SubClassOf synapsed by some 'alpha motor neuron'","title":"synapsed by"},{"location":"relations_guide/#recording-function","text":"Cellular function is recorded by linking GO biological process terms with the object properties 'capable of' and \u2018capable of part of\u2019","title":"Recording function"},{"location":"relations_guide/#capable-of","text":"Use this relation where the cell is capable of carrying out the entirety of the process For example, 'hilus cell of ovary' has the following subclass: 'capable of' some 'androgen secretion'","title":"'capable of'"},{"location":"relations_guide/#recording-neurotransmitter-for-neurons","text":"To record which neurotransmitter a neuron releases, use a 'capable of' relation to link the neuron to a subclass of GO neurotransmitter secretion that references a neurotransmitter type. This should be sufficient for autoclassification. e.g. 'medium spiny neuron' 'capable of' some 'gamma-aminobutyric acid secretion, neurotransmission'","title":"Recording neurotransmitter for neurons"},{"location":"relations_guide/#capable-of-part-of","text":"Use this relationship where only part of the process occurs in the cell type. e.g. 'retinal bipolar neuron' 'capable of part of' some 'visual perception'","title":"\u2018capable of part of\u2019"},{"location":"relations_guide/#recording-developmental-lineage","text":"Developmental lineage is recorded between cell types with the object property develops from (a transitive property), or in the case where there are no intermediates between the cells, 'directly develops from' (a non-transitive subproperty of develops_from ) For example, 'leukocyte' subClassOf develops from some 'hematopoietic stem cell'","title":"Recording developmental lineage"},{"location":"relations_guide/#recording-cell-markers","text":"Only markers that are necessary to define a cell type should be recorded.","title":"Recording cell markers"},{"location":"relations_guide/#cell-surface-protein-markers","text":"The cell ontology has a set of terms for recording cell surface markers. The most commonly used relation for recording markers is 'has plasma membrane part' . This object property is used to record cell surface markers, especially in immune cells. There are also more specific properties, 'has low plasma membrane amount' and 'has high plasma membrane amount' , that can be used at an editor's discretion. In each case, a term from the PRotein Ontology (PRO) or a protein complex term from the Gene Ontology (GO) is used as the object of the relation. For example, 'alpha-beta T cell' has the following equivalence axiom: 'T cell' and 'has plasma membrane part' some 'alpha-beta T cell receptor complex' Absence of a marker can be recorded using lacks_plasma_membrane_part Warning - this is used in place of the more accurate OWL expression \"NOT has_part some X*** in order to keep within the EL profile of OWL. It's use with a general class as a target can potentially lead to reasoning errors.","title":"cell surface (protein) markers"},{"location":"relations_guide/#recording-gene-markers","text":"","title":"recording gene markers"},{"location":"relations_guide/#expresses","text":"Use this to link a cell type to a gene or gene product that defines it: For example: 'lamp5 GABAergic cortical interneuron' EquivalentTo: interneuron and ('has soma location' some 'cerebral cortex') and ('capable of' some 'gamma-aminobutyric acid secretion, neurotransmission') and (expresses some 'lysosome-associated membrane glycoprotein 5') In FBbt, FlyBase Gene IDs are permitted here (using standard resolvable URL pattern). In CL currenly only PRO IDs are permitted. In PCL, a broader range of IDs have been used (depending on data sources used).","title":"\u2018expresses\u2019"},{"location":"relations_guide/#recording-cell-parts","text":"To record parts above the granularity of proteins and complexes, use a 'has part' relationship with an object from the Gene Ontology cellular_component branch. e.g. 'melanocyte' subClassOf 'has part' some 'melanosome' This GO term can be combined with a PATO quality term (e.g. for shape) where necessary, e.g. For example: 'mature basophil' subClassOf ('has part' some (nucleus and ('has characteristic' some lobed)))","title":"Recording cell parts"},{"location":"relations_guide/#recording-general-cellular-characteristics","text":"The ontology PATO , has a rich set of terms that can be used to record the general characteristics of cells, such as their morphology. These are recorded using 'has characteristic' . In choosing PATO terms, avoid those referring to some change in characteristic (e.g,.\u2019 increased branchiness\u2019). The following list of examples is not exhaustive:","title":"Recording general cellular characteristics"},{"location":"relations_guide/#recording-morphology","text":"PATO has a set of general morphology terms which may be applicable to cells. For example, erythrocyte subClassOf 'has characteristic' some biconcave PATO also has a set of terms for specific cell morphologies (mostly neuronal), e.g. \u2018Betz cell\u2019 subClassOf \u2018has characteristic\u2019 some \u2018standard pyramidal morphology\u2019","title":"Recording Morphology"},{"location":"relations_guide/#recording-nuclear-number","text":"To record the number of nuclei in a cell, use a PATO subclass under the term 'nucleate quality' with the 'has characteristic' relation. For example, platelet subClassOf ( 'has_characteristic' some anucleate ) Note - that PATO includes bridging axioms that infer part relationships based on these characteristics. e.g. cell and ('has characteristic' some multinucleate) SubClassOf 'has part' some nucleus","title":"Recording nuclear number"},{"location":"relations_guide/#taxon-constraints","text":"See https://oboacademy.github.io/obook/explanation/taxon-constraints-explainer/ .","title":"Taxon constraints"},{"location":"resolving_merge_conflicts/","text":"A guide to resolving merge conflicts \u00b6 When you come to merge your pull request, you may find that conflicts prevent automated merging back into the master. In some cases, GitHub supports resolution of these through its web interface. However, probably due to file size, this is not currently supported for cl-edit.owl. The majority of the time, the conflict is trivial - due to addition of new terms to the same point in the file. Because terms are ordered in the file by ID, this happens whenever two edits add terms without any intervening IDs. Trivial clashes are easy to spot - they involve whole term stanzas + declarations. Occassionally non-trivial clashes will happen when two pull requests include edits to the same term or even the same axiom. Ask an editor for help if you don't feel confident resolving these. SOP. \u00b6 Reserialise the Master file using the Ontology Development Kit (ODK). This requires setting up Docker and ODK. If not already set up, follow the instructions here . Open Docker. At the line command (PC) or Terminal (Mac), use the cd (change directory) command to navigate to the repository's src/ontology/ directory. For example, ''' cd PATH_TO_ONTOLOGY/src/ontology/ ''' Replace \"PATH_TO_ONTOLOGY\" with the actual file path to the ontology. If you need to orient yourself, use the '''pwd''' (present working directory) or '''ls''' (list) line commands. If you are resolving a conflict in an .owl file, run: ''' sh run.sh make normalize_src ''' If you are resolving a conflict in an .obo file, run: ''' sh run.sh make normalize_obo_src ''' In CL, edits sometimes result in creating a large amount of uninteded differences involving ^^xsd:string. If you see these differences after running the command above, they can be resolved by following the instructions here . Resolving conflicts in GitHub Desktop and VSCode: 1) Update Branches in GitHub Desktop: Checkout Master and pull to make sure your Master branch is up to date. Checkout the branch for the pull request and make sure it is up to date. Choose Branch > Update from Master to integrate the latest changes from the master branch. GitHub Desktop will detect the clash and suggest opening it in your text editor of choice (e.g., Atom, VSCode). 2) Resolve Conflicts in VSCode: Open the conflicting file in VSCode. VSCode will highlight the conflicting areas with markers: For trivial ordering problems, either manually delete the conflict markers ( <<<<<<< , ======= , >>>>>>> ) and ensure all necessary declarations are retained, or click on Accept Both Changes . This will automatically merge all term declarations from both the HEAD and incoming changes, removing the conflict markers. Important - Always check that the change does look trivial before making any changes. 3) Reserialise OWL Files: It is essential to reserialise after resolving conflicts to ensure consistency and proper formatting: sh run.sh make normalize_src 4) Complete the Resolution Process: In GitHub Desktop, review the changes, commit the resolved conflict, and push the updates back to GitHub. Check the resulting diffs on the Pull Request on GitHub. Once the checks have run and are successful, merge and delete the branch.","title":"Resolving merge conflicts"},{"location":"resolving_merge_conflicts/#a-guide-to-resolving-merge-conflicts","text":"When you come to merge your pull request, you may find that conflicts prevent automated merging back into the master. In some cases, GitHub supports resolution of these through its web interface. However, probably due to file size, this is not currently supported for cl-edit.owl. The majority of the time, the conflict is trivial - due to addition of new terms to the same point in the file. Because terms are ordered in the file by ID, this happens whenever two edits add terms without any intervening IDs. Trivial clashes are easy to spot - they involve whole term stanzas + declarations. Occassionally non-trivial clashes will happen when two pull requests include edits to the same term or even the same axiom. Ask an editor for help if you don't feel confident resolving these.","title":"A guide to resolving merge conflicts"},{"location":"resolving_merge_conflicts/#sop","text":"Reserialise the Master file using the Ontology Development Kit (ODK). This requires setting up Docker and ODK. If not already set up, follow the instructions here . Open Docker. At the line command (PC) or Terminal (Mac), use the cd (change directory) command to navigate to the repository's src/ontology/ directory. For example, ''' cd PATH_TO_ONTOLOGY/src/ontology/ ''' Replace \"PATH_TO_ONTOLOGY\" with the actual file path to the ontology. If you need to orient yourself, use the '''pwd''' (present working directory) or '''ls''' (list) line commands. If you are resolving a conflict in an .owl file, run: ''' sh run.sh make normalize_src ''' If you are resolving a conflict in an .obo file, run: ''' sh run.sh make normalize_obo_src ''' In CL, edits sometimes result in creating a large amount of uninteded differences involving ^^xsd:string. If you see these differences after running the command above, they can be resolved by following the instructions here . Resolving conflicts in GitHub Desktop and VSCode: 1) Update Branches in GitHub Desktop: Checkout Master and pull to make sure your Master branch is up to date. Checkout the branch for the pull request and make sure it is up to date. Choose Branch > Update from Master to integrate the latest changes from the master branch. GitHub Desktop will detect the clash and suggest opening it in your text editor of choice (e.g., Atom, VSCode). 2) Resolve Conflicts in VSCode: Open the conflicting file in VSCode. VSCode will highlight the conflicting areas with markers: For trivial ordering problems, either manually delete the conflict markers ( <<<<<<< , ======= , >>>>>>> ) and ensure all necessary declarations are retained, or click on Accept Both Changes . This will automatically merge all term declarations from both the HEAD and incoming changes, removing the conflict markers. Important - Always check that the change does look trivial before making any changes. 3) Reserialise OWL Files: It is essential to reserialise after resolving conflicts to ensure consistency and proper formatting: sh run.sh make normalize_src 4) Complete the Resolution Process: In GitHub Desktop, review the changes, commit the resolved conflict, and push the updates back to GitHub. Check the resulting diffs on the Pull Request on GitHub. Once the checks have run and are successful, merge and delete the branch.","title":"SOP."},{"location":"taxon-restrictions/","text":"The cell ontology (CL) follows certain conventions regarding taxon constraints. Please review the following pages before adding taxon constraints to CL terms. Explanation of taxon constraints can be found here: https://oboacademy.github.io/obook/explanation/taxon-constraints-explainer/ A how-to guide can be found here: https://oboacademy.github.io/obook/howto/add-taxon-restrictions/","title":"Taxon constraints"},{"location":"textual_definitions_SOP/","text":"Guide to writing textual definitions on CL \u00b6 Relevant background material \u00b6 Chris Mungall's blog post on ontology term definitions OBO foundry reference paper Background \u00b6 It is standard ontology engineering practise to aim for minimal, concise ontology term definitions. However many cell types can be reliably identified by more than one set of properties: functional, structural, gene expression. This makes it hard to choose which properties to include if we are aiming for a minimal definition. Users of the cell ontology also come from different disciplines/perspectives and have different types of information and levels of detail available when they annotate a term or browse a resource. We need to be able to support users from multiple disciplines with a definition that allows them to visualise and identify the cell type being defined. In some cases (e.g. transcriptomically defined types or 't-types') identification of the cell type requires links to reference data. Here are a couple of examples of minimal definitions that are correct but not useful to most users: We could minimally define a corneal endothelial cell as 'Any endothelial cell that is part of the cornea'. This may well be sufficient for an expert in the anatomy and biology of the cornea, but to most biologists, the term \"endothelial cell\" brings to mind the principal cell types of lymphatic or blood vessels. However, \"corneal endothelium\" refers to a monolayer of flat cells on the underside of the cornea. Similarly, a perfectly accurate minimal definition of a type II pneuomocyte is an epithelial cell that has an 'alveolar lamellar body' (a unique structure only found in these cell types). But this is useless information to a user who knows nothing about this structure (many biologists) or who is annotating data that does not resolve this structure. A second use of ontologies is to encode knowledge in the form of useful formal links between ontology terms. For example, in CL we record function and cell components via links to gene ontology terms, location via links to CL and lineage via links to other CL terms. Not all of this information is particularly useful for recognising a cell type, but it is of use to our users and so we often record it in CL using formal relationships. This is relevant to ontology definitions because it is good practise for formal and textual definitions to match, and textual definitions are the place we encode supporting references. We can't of course, include every known piece of information about a cell in a definition (e.g. all genes expressed). However extended information is useful to our users - especially where it includes potential marker sets and information relevant to human physiology and disease. To support this, we have an additioal extended description field which can contain information about additional marker sets, minor (secondary) functions and disease. It can also contain information about properties that may not apply to all subclasses - this is especially useful for t-types in the brain where there is typically sparse knowledge of the extent of variation in morphology and function under each t-type. In these cases, information should be included about where these proporites do apply. SOP \u00b6 Definition \u00b6 Text in the definition field should be no longer than one short paragraph and should follow a classic genus, differentia and gloss type structure: - genus : what type of cell is it (e.g. epithelial cell) - differentia : a list of properties that can be used to distinguish it from other similar cell types, especially those in the same tissue/organ context and those of the same genus. This SHOULD include location unless the term is so abstract that this is not possible. We should be liberal in listing properties here in order to support multiple communities who will have different types of data and understanding. Structural and functional properties are preferred over molecular markers unless cell types are named for these markers or they are generally accepted as definitional by the community. Care should be taken not to attach species-specific markers to species-general cell types. Please note that while CL supports recording multiple marker sets for cell types, ideally with provenance, evidence and confidence, this is supported is outside of the core definition text. - gloss : Additional information not required for identification, including but not limited to, all assertions recorded in formal local assertions not covered in the differentia. This can include information about developmental origins, processes the cell is capable of (for example these may be secondary processes like a tendon cell's role stimulating an immune response when damaged) or roles that the cell may have. Extended description text. \u00b6 This is optional descriptive information in the rdfs:comment value (although we may switch to a dedicated annotation property in future). The text should be referenced following standard academic practice (minirefs in text, e.g. Avola et al ., 2024). It can include: - Descriptions of marker genes and marker gene sets. These MUST include species and provenance, and ideally evidence (e.g. identified by use of the NS-Forest algorithm on dataset x; identified by in-situ hybridization) and confidence. - Information specific to only some species or subtypes (where the applicability is known this should be made clear). - Information about the role of the cell type more broadly in disease and physiology. The comment section may also be used to record evidence and and name/synonym disambiguation. Defining transcriptomic types (t-types): \u00b6 Some cell types are defined with reference to transcriptomic data. This is especially common in brain datasets. In these cases, naming is often based on semi-automated transfer of names that are based on some specific set of properties. We do not always know how widely those properties apply so need to be careful in choosing them for differentia. Extended multi-modal descriptions may be available, for example based on patch-seq data, but this is typically derived from very sparse data, so such information belongs in the extended definition, along with details of the brain regions where these properties have been assayed. Definitions for these follow a different pattern: - First sentence: \"A transcriptomically distinct { genus } with { description of primary differentia here }. Second sentence may include more differentia. - Gloss: see above - Last sentence: The standard transcriptomic reference data for this cell type can be found on the { site } under { human readable details of how to access dataset }, { human readable details of annotation key/value pair that marks the reference cell set. Example : label: A transcriptomically distinct intratelencephalic-projecting glutamatergic neuron with a soma found between cortical layer 2-4. The standard transcriptomic reference data for this cell type can be found on the CellxGene census under the collection: \"Transcriptomic cytoarchitecture reveals principles of human neocortex organization\", dataset: \"Supercluster: IT-projecting excitatory neurons\", Author Categories: \"CrossArea_subclass\", value: L2/3 IT. Comment: In the barrel cortex (of rodents), these neurons have thin-tufted apical dendrites, extend their axonal projections into L5 in the neocortex and have a hyperpolarised resting membrane potential (Harris & Shepherd 2016). Historically, these neurons were identified in cortical layer 2/3. MERFISH data shows that this intratelencephalic-projecting glutametergic neuron can have its soma in layer 2/3, 4B, 4C (Jorstad et al., 2023). The position of the soma in layer 4b and 4C is less frequent for this neuronal type in comparison to cortical layer 2/3. Note: the reference dataset should MUST also be referenced directly via an xref. _Axiomatisation of t-types: Axiomatisation of t-types is tricky for a number of reasons: 1. Transcriptomic hierarchy does not necessarily follow property based hierarchy, for example we might define SST cortical interneurons as any cortical interneuron expressing SST. However, in transcriptomic hierarchies, one SST expressing type (SST CHODL) is only distantly related to the other SST cells and so is treated as a disjoint type (e.g. see Jorstad et al., 2023). 2. We have limited knowledge about how widely particular properties of t-types apply. In the former case, it is sufficient to express the property with a subClassOf axiom (avoiding use of equivalent class axioms). In the latter, mention of the property should be confined to the extended description, or in some cases we may use annotation properties. For example, in linking a single transcriptomic cell type to multiple brain regions or layers, an annotation property must be used. Given the limited knowledge we have about how and whether they are unique to a particular cell type, care needs to be taken in adding formal axioms recording them. Where there is a possibility that it is important to limit clauses in EquivalentClass expressions. We have a standard pattern that can be used to convert transcriptomic heirarchies into SubClassOf hierarchies - using equivalence axioms with a 'has_examplar' clause with value cell set (see Tan et al., 2023 for details). However care should be taken in using this given the potential for inheritance of properties that don't apply to all subclusters in a transcriptomic hierarchy. A formal link to a defining cell set can be represented using subClassOf in order to avoid this. References \u00b6 Harris, Kenneth D., and Gordon M. G. Shepherd. 2015. \u201cThe Neocortical Circuit: Themes and Variations.\u201d Nature Neuroscience 18 (2): 170\u201381. https://doi.org/10.1038/nn.3917. Jorstad, Nikolas L., Jennie Close, Nelson Johansen, Anna Marie Yanny, Eliza R. Barkan, Kyle J. Travaglini, Darren Bertagnolli, et al. 2023. \u201cTranscriptomic Cytoarchitecture Reveals Principles of Human Neocortex Organization.\u201d Science 382 (6667): eadf6812. https://doi.org/10.1126/science.adf6812. Tan, Shawn Zheng Kai, Huseyin Kir, Brian D. Aevermann, Tom Gillespie, Nomi Harris, Michael J. Hawrylycz, Nikolas L. Jorstad, et al. 2023. \u201cBrain Data Standards - A Method for Building Data-Driven Cell-Type Ontologies.\u201d Scientific Data 10 (1): 50. https://doi.org/10.1038/s41597-022-01886-2.","title":"textual definitions SOP"},{"location":"textual_definitions_SOP/#guide-to-writing-textual-definitions-on-cl","text":"","title":"Guide to writing textual definitions on CL"},{"location":"textual_definitions_SOP/#relevant-background-material","text":"Chris Mungall's blog post on ontology term definitions OBO foundry reference paper","title":"Relevant background material"},{"location":"textual_definitions_SOP/#background","text":"It is standard ontology engineering practise to aim for minimal, concise ontology term definitions. However many cell types can be reliably identified by more than one set of properties: functional, structural, gene expression. This makes it hard to choose which properties to include if we are aiming for a minimal definition. Users of the cell ontology also come from different disciplines/perspectives and have different types of information and levels of detail available when they annotate a term or browse a resource. We need to be able to support users from multiple disciplines with a definition that allows them to visualise and identify the cell type being defined. In some cases (e.g. transcriptomically defined types or 't-types') identification of the cell type requires links to reference data. Here are a couple of examples of minimal definitions that are correct but not useful to most users: We could minimally define a corneal endothelial cell as 'Any endothelial cell that is part of the cornea'. This may well be sufficient for an expert in the anatomy and biology of the cornea, but to most biologists, the term \"endothelial cell\" brings to mind the principal cell types of lymphatic or blood vessels. However, \"corneal endothelium\" refers to a monolayer of flat cells on the underside of the cornea. Similarly, a perfectly accurate minimal definition of a type II pneuomocyte is an epithelial cell that has an 'alveolar lamellar body' (a unique structure only found in these cell types). But this is useless information to a user who knows nothing about this structure (many biologists) or who is annotating data that does not resolve this structure. A second use of ontologies is to encode knowledge in the form of useful formal links between ontology terms. For example, in CL we record function and cell components via links to gene ontology terms, location via links to CL and lineage via links to other CL terms. Not all of this information is particularly useful for recognising a cell type, but it is of use to our users and so we often record it in CL using formal relationships. This is relevant to ontology definitions because it is good practise for formal and textual definitions to match, and textual definitions are the place we encode supporting references. We can't of course, include every known piece of information about a cell in a definition (e.g. all genes expressed). However extended information is useful to our users - especially where it includes potential marker sets and information relevant to human physiology and disease. To support this, we have an additioal extended description field which can contain information about additional marker sets, minor (secondary) functions and disease. It can also contain information about properties that may not apply to all subclasses - this is especially useful for t-types in the brain where there is typically sparse knowledge of the extent of variation in morphology and function under each t-type. In these cases, information should be included about where these proporites do apply.","title":"Background"},{"location":"textual_definitions_SOP/#sop","text":"","title":"SOP"},{"location":"textual_definitions_SOP/#definition","text":"Text in the definition field should be no longer than one short paragraph and should follow a classic genus, differentia and gloss type structure: - genus : what type of cell is it (e.g. epithelial cell) - differentia : a list of properties that can be used to distinguish it from other similar cell types, especially those in the same tissue/organ context and those of the same genus. This SHOULD include location unless the term is so abstract that this is not possible. We should be liberal in listing properties here in order to support multiple communities who will have different types of data and understanding. Structural and functional properties are preferred over molecular markers unless cell types are named for these markers or they are generally accepted as definitional by the community. Care should be taken not to attach species-specific markers to species-general cell types. Please note that while CL supports recording multiple marker sets for cell types, ideally with provenance, evidence and confidence, this is supported is outside of the core definition text. - gloss : Additional information not required for identification, including but not limited to, all assertions recorded in formal local assertions not covered in the differentia. This can include information about developmental origins, processes the cell is capable of (for example these may be secondary processes like a tendon cell's role stimulating an immune response when damaged) or roles that the cell may have.","title":"Definition"},{"location":"textual_definitions_SOP/#extended-description-text","text":"This is optional descriptive information in the rdfs:comment value (although we may switch to a dedicated annotation property in future). The text should be referenced following standard academic practice (minirefs in text, e.g. Avola et al ., 2024). It can include: - Descriptions of marker genes and marker gene sets. These MUST include species and provenance, and ideally evidence (e.g. identified by use of the NS-Forest algorithm on dataset x; identified by in-situ hybridization) and confidence. - Information specific to only some species or subtypes (where the applicability is known this should be made clear). - Information about the role of the cell type more broadly in disease and physiology. The comment section may also be used to record evidence and and name/synonym disambiguation.","title":"Extended description text."},{"location":"textual_definitions_SOP/#defining-transcriptomic-types-t-types","text":"Some cell types are defined with reference to transcriptomic data. This is especially common in brain datasets. In these cases, naming is often based on semi-automated transfer of names that are based on some specific set of properties. We do not always know how widely those properties apply so need to be careful in choosing them for differentia. Extended multi-modal descriptions may be available, for example based on patch-seq data, but this is typically derived from very sparse data, so such information belongs in the extended definition, along with details of the brain regions where these properties have been assayed. Definitions for these follow a different pattern: - First sentence: \"A transcriptomically distinct { genus } with { description of primary differentia here }. Second sentence may include more differentia. - Gloss: see above - Last sentence: The standard transcriptomic reference data for this cell type can be found on the { site } under { human readable details of how to access dataset }, { human readable details of annotation key/value pair that marks the reference cell set. Example : label: A transcriptomically distinct intratelencephalic-projecting glutamatergic neuron with a soma found between cortical layer 2-4. The standard transcriptomic reference data for this cell type can be found on the CellxGene census under the collection: \"Transcriptomic cytoarchitecture reveals principles of human neocortex organization\", dataset: \"Supercluster: IT-projecting excitatory neurons\", Author Categories: \"CrossArea_subclass\", value: L2/3 IT. Comment: In the barrel cortex (of rodents), these neurons have thin-tufted apical dendrites, extend their axonal projections into L5 in the neocortex and have a hyperpolarised resting membrane potential (Harris & Shepherd 2016). Historically, these neurons were identified in cortical layer 2/3. MERFISH data shows that this intratelencephalic-projecting glutametergic neuron can have its soma in layer 2/3, 4B, 4C (Jorstad et al., 2023). The position of the soma in layer 4b and 4C is less frequent for this neuronal type in comparison to cortical layer 2/3. Note: the reference dataset should MUST also be referenced directly via an xref. _Axiomatisation of t-types: Axiomatisation of t-types is tricky for a number of reasons: 1. Transcriptomic hierarchy does not necessarily follow property based hierarchy, for example we might define SST cortical interneurons as any cortical interneuron expressing SST. However, in transcriptomic hierarchies, one SST expressing type (SST CHODL) is only distantly related to the other SST cells and so is treated as a disjoint type (e.g. see Jorstad et al., 2023). 2. We have limited knowledge about how widely particular properties of t-types apply. In the former case, it is sufficient to express the property with a subClassOf axiom (avoiding use of equivalent class axioms). In the latter, mention of the property should be confined to the extended description, or in some cases we may use annotation properties. For example, in linking a single transcriptomic cell type to multiple brain regions or layers, an annotation property must be used. Given the limited knowledge we have about how and whether they are unique to a particular cell type, care needs to be taken in adding formal axioms recording them. Where there is a possibility that it is important to limit clauses in EquivalentClass expressions. We have a standard pattern that can be used to convert transcriptomic heirarchies into SubClassOf hierarchies - using equivalence axioms with a 'has_examplar' clause with value cell set (see Tan et al., 2023 for details). However care should be taken in using this given the potential for inheritance of properties that don't apply to all subclusters in a transcriptomic hierarchy. A formal link to a defining cell set can be represented using subClassOf in order to avoid this.","title":"Defining transcriptomic types (t-types):"},{"location":"textual_definitions_SOP/#references","text":"Harris, Kenneth D., and Gordon M. G. Shepherd. 2015. \u201cThe Neocortical Circuit: Themes and Variations.\u201d Nature Neuroscience 18 (2): 170\u201381. https://doi.org/10.1038/nn.3917. Jorstad, Nikolas L., Jennie Close, Nelson Johansen, Anna Marie Yanny, Eliza R. Barkan, Kyle J. Travaglini, Darren Bertagnolli, et al. 2023. \u201cTranscriptomic Cytoarchitecture Reveals Principles of Human Neocortex Organization.\u201d Science 382 (6667): eadf6812. https://doi.org/10.1126/science.adf6812. Tan, Shawn Zheng Kai, Huseyin Kir, Brian D. Aevermann, Tom Gillespie, Nomi Harris, Michael J. Hawrylycz, Nikolas L. Jorstad, et al. 2023. \u201cBrain Data Standards - A Method for Building Data-Driven Cell-Type Ontologies.\u201d Scientific Data 10 (1): 50. https://doi.org/10.1038/s41597-022-01886-2.","title":"References"},{"location":"odk-workflows/","text":"Default ODK Workflows \u00b6 Daily Editors Workflow Release Workflow Manage your ODK Repository Setting up Docker for ODK Imports management Managing the documentation Managing your Automated Testing","title":"Overview"},{"location":"odk-workflows/#default-odk-workflows","text":"Daily Editors Workflow Release Workflow Manage your ODK Repository Setting up Docker for ODK Imports management Managing the documentation Managing your Automated Testing","title":"Default ODK Workflows"},{"location":"odk-workflows/ContinuousIntegration/","text":"Introduction to Continuous Integration Workflows with ODK \u00b6 Historically, most repos have been using Travis CI for continuous integration testing and building, but due to runtime restrictions, we recently switched a lot of our repos to GitHub actions. You can set up your repo with CI by adding this to your configuration file (src/ontology/cl-odk.yaml): ci: - github_actions When updateing your repo , you will notice a new file being added: .github/workflows/qc.yml . This file contains your CI logic, so if you need to change, or add anything, this is the place! Alternatively, if your repo is in GitLab instead of GitHub, you can set up your repo with GitLab CI by adding this to your configuration file (src/ontology/cl-odk.yaml): ci: - gitlab-ci This will add a file called .gitlab-ci.yml in the root of your repo.","title":"Continuous Integration"},{"location":"odk-workflows/ContinuousIntegration/#introduction-to-continuous-integration-workflows-with-odk","text":"Historically, most repos have been using Travis CI for continuous integration testing and building, but due to runtime restrictions, we recently switched a lot of our repos to GitHub actions. You can set up your repo with CI by adding this to your configuration file (src/ontology/cl-odk.yaml): ci: - github_actions When updateing your repo , you will notice a new file being added: .github/workflows/qc.yml . This file contains your CI logic, so if you need to change, or add anything, this is the place! Alternatively, if your repo is in GitLab instead of GitHub, you can set up your repo with GitLab CI by adding this to your configuration file (src/ontology/cl-odk.yaml): ci: - gitlab-ci This will add a file called .gitlab-ci.yml in the root of your repo.","title":"Introduction to Continuous Integration Workflows with ODK"},{"location":"odk-workflows/EditorsWorkflow/","text":"Editors Workflow \u00b6 The editors workflow is one of the formal workflows to ensure that the ontology is developed correctly according to ontology engineering principles. There are a few different editors workflows: Local editing workflow: Editing the ontology in your local environment by hand, using tools such as Prot\u00e9g\u00e9, ROBOT templates or DOSDP patterns. Completely automated data pipeline (GitHub Actions) DROID workflow This document only covers the first editing workflow, but more will be added in the future Local editing workflow \u00b6 Workflow requirements: git github docker editing tool of choice, e.g. Prot\u00e9g\u00e9, your favourite text editor, etc 1. Create issue \u00b6 Ensure that there is a ticket on your issue tracker that describes the change you are about to make. While this seems optional, this is a very important part of the social contract of building an ontology - no change to the ontology should be performed without a good ticket, describing the motivation and nature of the intended change. 2. Update main branch \u00b6 In your local environment (e.g. your laptop), make sure you are on the main (prev. master ) branch and ensure that you have all the upstream changes, for example: git checkout master git pull 3. Create feature branch \u00b6 Create a new branch. Per convention, we try to use meaningful branch names such as: - issue23removeprocess (where issue 23 is the related issue on GitHub) - issue26addcontributor - release20210101 (for releases) On your command line, this looks like this: git checkout -b issue23removeprocess 4. Perform edit \u00b6 Using your editor of choice, perform the intended edit. For example: Prot\u00e9g\u00e9 Open src/ontology/cl-edit.owl in Prot\u00e9g\u00e9 Make the change Save the file TextEdit Open src/ontology/cl-edit.owl in TextEdit (or Sublime, Atom, Vim, Nano) Make the change Save the file Consider the following when making the edit. According to our development philosophy, the only places that should be manually edited are: src/ontology/cl-edit.owl Any ROBOT templates you chose to use (the TSV files only) Any DOSDP data tables you chose to use (the TSV files, and potentially the associated patterns) components (anything in src/ontology/components ), see here . Imports should not be edited (any edits will be flushed out with the next update). However, refreshing imports is a potentially breaking change - and is discussed elsewhere . Changes should usually be small. Adding or changing 1 term is great. Adding or changing 10 related terms is ok. Adding or changing 100 or more terms at once should be considered very carefully. 4. Check the Git diff \u00b6 This step is very important. Rather than simply trusting your change had the intended effect, we should always use a git diff as a first pass for sanity checking. In our experience, having a visual git client like GitHub Desktop or sourcetree is really helpful for this part. In case you prefer the command line: git status git diff 5. Quality control \u00b6 Now it's time to run your quality control checks. This can either happen locally ( 5a ) or through your continuous integration system ( 7/5b ). 5a. Local testing \u00b6 If you chose to run your test locally: sh run.sh make IMP=false test This will run the whole set of configured ODK tests on including your change. If you have a complex DOSDP pattern pipeline you may want to add PAT=false to skip the potentially lengthy process of rebuilding the patterns. sh run.sh make IMP=false PAT=false test 6. Pull request \u00b6 When you are happy with the changes, you commit your changes to your feature branch, push them upstream (to GitHub) and create a pull request. For example: git add NAMEOFCHANGEDFILES git commit -m \"Added biological process term #12\" git push -u origin issue23removeprocess Then you go to your project on GitHub, and create a new pull request from the branch, for example: https://github.com/INCATools/ontology-development-kit/pulls There is a lot of great advise on how to write pull requests, but at the very least you should: - mention the tickets affected: see #23 to link to a related ticket, or fixes #23 if, by merging this pull request, the ticket is fixed. Tickets in the latter case will be closed automatically by GitHub when the pull request is merged. - summarise the changes in a few sentences. Consider the reviewer: what would they want to know right away. - If the diff is large, provide instructions on how to review the pull request best (sometimes, there are many changed files, but only one important change). 7/5b. Continuous Integration Testing \u00b6 If you didn't run and local quality control checks (see 5a ), you should have Continuous Integration (CI) set up, for example: - Travis - GitHub Actions More on how to set this up here . Once the pull request is created, the CI will automatically trigger. If all is fine, it will show up green, otherwise red. 8. Community review \u00b6 Once all the automatic tests have passed, it is important to put a second set of eyes on the pull request. Ontologies are inherently social - as in that they represent some kind of community consensus on how a domain is organised conceptually. This seems high brow talk, but it is very important that as an ontology editor, you have your work validated by the community you are trying to serve (e.g. your colleagues, other contributors etc.). In our experience, it is hard to get more than one review on a pull request - two is great. You can set up GitHub branch protection to actually require a review before a pull request can be merged! We recommend this. This step seems daunting to some hopefully under-resourced ontologies, but we recommend to put this high up on your list of priorities - train a colleague, reach out! 9. Merge and cleanup \u00b6 When the QC is green and the reviews are in (approvals), it is time to merge the pull request. After the pull request is merged, remember to delete the branch as well (this option will show up as a big button right after you have merged the pull request). If you have not done so, close all the associated tickets fixed by the pull request. 10. Changelog (Optional) \u00b6 It is sometimes difficult to keep track of changes made to an ontology. Some ontology teams opt to document changes in a changelog (simply a text file in your repository) so that when release day comes, you know everything you have changed. This is advisable at least for major changes (such as a new release system, a new pattern or template etc.).","title":"Editors Workflow"},{"location":"odk-workflows/EditorsWorkflow/#editors-workflow","text":"The editors workflow is one of the formal workflows to ensure that the ontology is developed correctly according to ontology engineering principles. There are a few different editors workflows: Local editing workflow: Editing the ontology in your local environment by hand, using tools such as Prot\u00e9g\u00e9, ROBOT templates or DOSDP patterns. Completely automated data pipeline (GitHub Actions) DROID workflow This document only covers the first editing workflow, but more will be added in the future","title":"Editors Workflow"},{"location":"odk-workflows/EditorsWorkflow/#local-editing-workflow","text":"Workflow requirements: git github docker editing tool of choice, e.g. Prot\u00e9g\u00e9, your favourite text editor, etc","title":"Local editing workflow"},{"location":"odk-workflows/EditorsWorkflow/#1-create-issue","text":"Ensure that there is a ticket on your issue tracker that describes the change you are about to make. While this seems optional, this is a very important part of the social contract of building an ontology - no change to the ontology should be performed without a good ticket, describing the motivation and nature of the intended change.","title":"1. Create issue"},{"location":"odk-workflows/EditorsWorkflow/#2-update-main-branch","text":"In your local environment (e.g. your laptop), make sure you are on the main (prev. master ) branch and ensure that you have all the upstream changes, for example: git checkout master git pull","title":"2. Update main branch"},{"location":"odk-workflows/EditorsWorkflow/#3-create-feature-branch","text":"Create a new branch. Per convention, we try to use meaningful branch names such as: - issue23removeprocess (where issue 23 is the related issue on GitHub) - issue26addcontributor - release20210101 (for releases) On your command line, this looks like this: git checkout -b issue23removeprocess","title":"3. Create feature branch"},{"location":"odk-workflows/EditorsWorkflow/#4-perform-edit","text":"Using your editor of choice, perform the intended edit. For example: Prot\u00e9g\u00e9 Open src/ontology/cl-edit.owl in Prot\u00e9g\u00e9 Make the change Save the file TextEdit Open src/ontology/cl-edit.owl in TextEdit (or Sublime, Atom, Vim, Nano) Make the change Save the file Consider the following when making the edit. According to our development philosophy, the only places that should be manually edited are: src/ontology/cl-edit.owl Any ROBOT templates you chose to use (the TSV files only) Any DOSDP data tables you chose to use (the TSV files, and potentially the associated patterns) components (anything in src/ontology/components ), see here . Imports should not be edited (any edits will be flushed out with the next update). However, refreshing imports is a potentially breaking change - and is discussed elsewhere . Changes should usually be small. Adding or changing 1 term is great. Adding or changing 10 related terms is ok. Adding or changing 100 or more terms at once should be considered very carefully.","title":"4. Perform edit"},{"location":"odk-workflows/EditorsWorkflow/#4-check-the-git-diff","text":"This step is very important. Rather than simply trusting your change had the intended effect, we should always use a git diff as a first pass for sanity checking. In our experience, having a visual git client like GitHub Desktop or sourcetree is really helpful for this part. In case you prefer the command line: git status git diff","title":"4. Check the Git diff"},{"location":"odk-workflows/EditorsWorkflow/#5-quality-control","text":"Now it's time to run your quality control checks. This can either happen locally ( 5a ) or through your continuous integration system ( 7/5b ).","title":"5. Quality control"},{"location":"odk-workflows/EditorsWorkflow/#5a-local-testing","text":"If you chose to run your test locally: sh run.sh make IMP=false test This will run the whole set of configured ODK tests on including your change. If you have a complex DOSDP pattern pipeline you may want to add PAT=false to skip the potentially lengthy process of rebuilding the patterns. sh run.sh make IMP=false PAT=false test","title":"5a. Local testing"},{"location":"odk-workflows/EditorsWorkflow/#6-pull-request","text":"When you are happy with the changes, you commit your changes to your feature branch, push them upstream (to GitHub) and create a pull request. For example: git add NAMEOFCHANGEDFILES git commit -m \"Added biological process term #12\" git push -u origin issue23removeprocess Then you go to your project on GitHub, and create a new pull request from the branch, for example: https://github.com/INCATools/ontology-development-kit/pulls There is a lot of great advise on how to write pull requests, but at the very least you should: - mention the tickets affected: see #23 to link to a related ticket, or fixes #23 if, by merging this pull request, the ticket is fixed. Tickets in the latter case will be closed automatically by GitHub when the pull request is merged. - summarise the changes in a few sentences. Consider the reviewer: what would they want to know right away. - If the diff is large, provide instructions on how to review the pull request best (sometimes, there are many changed files, but only one important change).","title":"6. Pull request"},{"location":"odk-workflows/EditorsWorkflow/#75b-continuous-integration-testing","text":"If you didn't run and local quality control checks (see 5a ), you should have Continuous Integration (CI) set up, for example: - Travis - GitHub Actions More on how to set this up here . Once the pull request is created, the CI will automatically trigger. If all is fine, it will show up green, otherwise red.","title":"7/5b. Continuous Integration Testing"},{"location":"odk-workflows/EditorsWorkflow/#8-community-review","text":"Once all the automatic tests have passed, it is important to put a second set of eyes on the pull request. Ontologies are inherently social - as in that they represent some kind of community consensus on how a domain is organised conceptually. This seems high brow talk, but it is very important that as an ontology editor, you have your work validated by the community you are trying to serve (e.g. your colleagues, other contributors etc.). In our experience, it is hard to get more than one review on a pull request - two is great. You can set up GitHub branch protection to actually require a review before a pull request can be merged! We recommend this. This step seems daunting to some hopefully under-resourced ontologies, but we recommend to put this high up on your list of priorities - train a colleague, reach out!","title":"8. Community review"},{"location":"odk-workflows/EditorsWorkflow/#9-merge-and-cleanup","text":"When the QC is green and the reviews are in (approvals), it is time to merge the pull request. After the pull request is merged, remember to delete the branch as well (this option will show up as a big button right after you have merged the pull request). If you have not done so, close all the associated tickets fixed by the pull request.","title":"9. Merge and cleanup"},{"location":"odk-workflows/EditorsWorkflow/#10-changelog-optional","text":"It is sometimes difficult to keep track of changes made to an ontology. Some ontology teams opt to document changes in a changelog (simply a text file in your repository) so that when release day comes, you know everything you have changed. This is advisable at least for major changes (such as a new release system, a new pattern or template etc.).","title":"10. Changelog (Optional)"},{"location":"odk-workflows/ManageAutomatedTest/","text":"Constraint violation checks \u00b6 We can define custom checks using SPARQL . SPARQL queries define bad modelling patterns (missing labels, misspelt URIs, and many more) in the ontology. If these queries return any results, then the build will fail. Custom checks are designed to be run as part of GitHub Actions Continuous Integration testing, but they can also run locally. Steps to add a constraint violation check: \u00b6 Add the SPARQL query in src/sparql . The name of the file should end with -violation.sparql . Please give a name that helps to understand which violation the query wants to check. Add the name of the new file to odk configuration file src/ontology/uberon-odk.yaml : Include the name of the file (without the -violation.sparql part) to the list inside the key custom_sparql_checks that is inside robot_report key. If the robot_report or custom_sparql_checks keys are not available, please add this code block to the end of the file. yaml robot_report: release_reports: False fail_on: ERROR use_labels: False custom_profile: True report_on: - edit custom_sparql_checks: - name-of-the-file-check 3. Update the repository so your new SPARQL check will be included in the QC. sh run.sh make update_repo","title":"ManageAutomatedTest"},{"location":"odk-workflows/ManageAutomatedTest/#constraint-violation-checks","text":"We can define custom checks using SPARQL . SPARQL queries define bad modelling patterns (missing labels, misspelt URIs, and many more) in the ontology. If these queries return any results, then the build will fail. Custom checks are designed to be run as part of GitHub Actions Continuous Integration testing, but they can also run locally.","title":"Constraint violation checks"},{"location":"odk-workflows/ManageAutomatedTest/#steps-to-add-a-constraint-violation-check","text":"Add the SPARQL query in src/sparql . The name of the file should end with -violation.sparql . Please give a name that helps to understand which violation the query wants to check. Add the name of the new file to odk configuration file src/ontology/uberon-odk.yaml : Include the name of the file (without the -violation.sparql part) to the list inside the key custom_sparql_checks that is inside robot_report key. If the robot_report or custom_sparql_checks keys are not available, please add this code block to the end of the file. yaml robot_report: release_reports: False fail_on: ERROR use_labels: False custom_profile: True report_on: - edit custom_sparql_checks: - name-of-the-file-check 3. Update the repository so your new SPARQL check will be included in the QC. sh run.sh make update_repo","title":"Steps to add a constraint violation check:"},{"location":"odk-workflows/ManageDocumentation/","text":"Updating the Documentation \u00b6 The documentation for CL is managed in two places (relative to the repository root): The docs directory contains all the files that pertain to the content of the documentation (more below) the mkdocs.yaml file contains the documentation config, in particular its navigation bar and theme. The documentation is hosted using GitHub pages, on a special branch of the repository (called gh-pages ). It is important that this branch is never deleted - it contains all the files GitHub pages needs to render and deploy the site. It is also important to note that the gh-pages branch should never be edited manually . All changes to the docs happen inside the docs directory on the main branch. Editing the docs \u00b6 Changing content \u00b6 All the documentation is contained in the docs directory, and is managed in Markdown . Markdown is a very simple and convenient way to produce text documents with formatting instructions, and is very easy to learn - it is also used, for example, in GitHub issues. This is a normal editing workflow: Open the .md file you want to change in an editor of choice (a simple text editor is often best). IMPORTANT : Do not edit any files in the docs/odk-workflows/ directory. These files are managed by the ODK system and will be overwritten when the repository is upgraded! If you wish to change these files, make an issue on the ODK issue tracker . Perform the edit and save the file Commit the file to a branch, and create a pull request as usual. If your development team likes your changes, merge the docs into master branch. Deploy the documentation (see below) Deploy the documentation \u00b6 The documentation is not automatically updated from the Markdown, and needs to be deployed deliberately. To do this, perform the following steps: In your terminal, navigate to the edit directory of your ontology, e.g.: cd cl/src/ontology Now you are ready to build the docs as follows: sh run.sh make update_docs Mkdocs now sets off to build the site from the markdown pages. You will be asked to Enter your username Enter your password (see here for using GitHub access tokens instead) IMPORTANT : Using password based authentication will be deprecated this year (2021). Make sure you read up on personal access tokens if that happens! If everything was successful, you will see a message similar to this one: INFO - Your documentation should shortly be available at: https://obophenotype.github.io/cell-ontology/ 3. Just to double check, you can now navigate to your documentation pages (usually https://obophenotype.github.io/cell-ontology/). Just make sure you give GitHub 2-5 minutes to build the pages!","title":"Managing the documentation"},{"location":"odk-workflows/ManageDocumentation/#updating-the-documentation","text":"The documentation for CL is managed in two places (relative to the repository root): The docs directory contains all the files that pertain to the content of the documentation (more below) the mkdocs.yaml file contains the documentation config, in particular its navigation bar and theme. The documentation is hosted using GitHub pages, on a special branch of the repository (called gh-pages ). It is important that this branch is never deleted - it contains all the files GitHub pages needs to render and deploy the site. It is also important to note that the gh-pages branch should never be edited manually . All changes to the docs happen inside the docs directory on the main branch.","title":"Updating the Documentation"},{"location":"odk-workflows/ManageDocumentation/#editing-the-docs","text":"","title":"Editing the docs"},{"location":"odk-workflows/ManageDocumentation/#changing-content","text":"All the documentation is contained in the docs directory, and is managed in Markdown . Markdown is a very simple and convenient way to produce text documents with formatting instructions, and is very easy to learn - it is also used, for example, in GitHub issues. This is a normal editing workflow: Open the .md file you want to change in an editor of choice (a simple text editor is often best). IMPORTANT : Do not edit any files in the docs/odk-workflows/ directory. These files are managed by the ODK system and will be overwritten when the repository is upgraded! If you wish to change these files, make an issue on the ODK issue tracker . Perform the edit and save the file Commit the file to a branch, and create a pull request as usual. If your development team likes your changes, merge the docs into master branch. Deploy the documentation (see below)","title":"Changing content"},{"location":"odk-workflows/ManageDocumentation/#deploy-the-documentation","text":"The documentation is not automatically updated from the Markdown, and needs to be deployed deliberately. To do this, perform the following steps: In your terminal, navigate to the edit directory of your ontology, e.g.: cd cl/src/ontology Now you are ready to build the docs as follows: sh run.sh make update_docs Mkdocs now sets off to build the site from the markdown pages. You will be asked to Enter your username Enter your password (see here for using GitHub access tokens instead) IMPORTANT : Using password based authentication will be deprecated this year (2021). Make sure you read up on personal access tokens if that happens! If everything was successful, you will see a message similar to this one: INFO - Your documentation should shortly be available at: https://obophenotype.github.io/cell-ontology/ 3. Just to double check, you can now navigate to your documentation pages (usually https://obophenotype.github.io/cell-ontology/). Just make sure you give GitHub 2-5 minutes to build the pages!","title":"Deploy the documentation"},{"location":"odk-workflows/ReleaseWorkflow/","text":"The release workflow \u00b6 The release workflow recommended by the ODK is based on GitHub releases and works as follows: Run a release with the ODK Review the release Merge to main branch Create a GitHub release These steps are outlined in detail in the following. Run a release with the ODK \u00b6 Preparation: Ensure that all your pull requests are merged into your main (master) branch Make sure that all changes to master are committed to GitHub ( git status should say that there are no modified files) Locally make sure you have the latest changes from master ( git pull ) Checkout a new branch (e.g. git checkout -b release-2021-01-01 ) You may or may not want to refresh your imports as part of your release strategy (see here ) Make sure you have the latest ODK installed by running docker pull obolibrary/odkfull To actually run the release, you: Open a command line terminal window and navigate to the src/ontology directory ( cd cl/src/ontology ) Run release pipeline: sh run.sh make prepare_release -B . Note that for some ontologies, this process can take up to 90 minutes - especially if there are large ontologies you depend on, like PRO or CHEBI. If everything went well, you should see the following output on your machine: Release files are now in ../.. - now you should commit, push and make a release on your git hosting site such as GitHub or GitLab . This will create all the specified release targets (OBO, OWL, JSON, and the variants, ont-full and ont-base) and copy them into your release directory (the top level of your repo). Review the release \u00b6 (Optional) Rough check. This step is frequently skipped, but for the more paranoid among us (like the author of this doc), this is a 3 minute additional effort for some peace of mind. Open the main release (cl.owl) in you favourite development environment (i.e. Prot\u00e9g\u00e9) and eyeball the hierarchy. We recommend two simple checks: Does the very top level of the hierarchy look ok? This means that all new terms have been imported/updated correctly. Does at least one change that you know should be in this release appear? For example, a new class. This means that the release was actually based on the recent edit file. Commit your changes to the branch and make a pull request In your GitHub pull request, review the following three files in detail (based on our experience): cl.obo - this reflects a useful subset of the whole ontology (everything that can be covered by OBO format). OBO format has that speaking for it: it is very easy to review! cl-base.owl - this reflects the asserted axioms in your ontology that you have actually edited. Ideally also take a look at cl-full.owl , which may reveal interesting new inferences you did not know about. Note that the diff of this file is sometimes quite large. Like with every pull request, we recommend to always employ a second set of eyes when reviewing a PR! Merge the main branch \u00b6 Once your CI checks have passed, and your reviews are completed, you can now merge the branch into your main branch (don't forget to delete the branch afterwards - a big button will appear after the merge is finished). Create a GitHub release \u00b6 Go to your releases page on GitHub by navigating to your repository, and then clicking on releases (usually on the right, for example: https://github.com/obophenotype/cell-ontology/releases). Then click \"Draft new release\" As the tag version you need to choose the date on which your ontologies were build. You can find this, for example, by looking at the cl.obo file and check the data-version: property. The date needs to be prefixed with a v , so, for example v2020-02-06 . You can write whatever you want in the release title, but we typically write the date again. The description underneath should contain a concise list of changes or term additions. Click \"Publish release\". Done. Debugging typical ontology release problems \u00b6 Problems with memory \u00b6 When you are dealing with large ontologies, you need a lot of memory. When you see error messages relating to large ontologies such as CHEBI, PRO, NCBITAXON, or Uberon, you should think of memory first, see here . Problems when using OBO format based tools \u00b6 Sometimes you will get cryptic error messages when using legacy tools using OBO format, such as the ontology release tool (OORT), which is also available as part of the ODK docker container. In these cases, you need to track down what axiom or annotation actually caused the breakdown. In our experience (in about 60% of the cases) the problem lies with duplicate annotations ( def , comment ) which are illegal in OBO. Here is an example recipe of how to deal with such a problem: If you get a message like make: *** [cl.Makefile:84: oort] Error 255 you might have a OORT error. To debug this, in your terminal enter sh run.sh make IMP=false PAT=false oort -B (assuming you are already in the ontology folder in your directory) This should show you where the error is in the log (eg multiple different definitions) WARNING: THE FIX BELOW IS NOT IDEAL, YOU SHOULD ALWAYS TRY TO FIX UPSTREAM IF POSSIBLE Open cl-edit.owl in Prot\u00e9g\u00e9 and find the offending term and delete all offending issue (e.g. delete ALL definition, if the problem was \"multiple def tags not allowed\") and save. *While this is not idea, as it will remove all definitions from that term, it will be added back again when the term is fixed in the ontology it was imported from and added back in. Rerun sh run.sh make IMP=false PAT=false oort -B and if it all passes, commit your changes to a branch and make a pull request as usual.","title":"Release Workflow"},{"location":"odk-workflows/ReleaseWorkflow/#the-release-workflow","text":"The release workflow recommended by the ODK is based on GitHub releases and works as follows: Run a release with the ODK Review the release Merge to main branch Create a GitHub release These steps are outlined in detail in the following.","title":"The release workflow"},{"location":"odk-workflows/ReleaseWorkflow/#run-a-release-with-the-odk","text":"Preparation: Ensure that all your pull requests are merged into your main (master) branch Make sure that all changes to master are committed to GitHub ( git status should say that there are no modified files) Locally make sure you have the latest changes from master ( git pull ) Checkout a new branch (e.g. git checkout -b release-2021-01-01 ) You may or may not want to refresh your imports as part of your release strategy (see here ) Make sure you have the latest ODK installed by running docker pull obolibrary/odkfull To actually run the release, you: Open a command line terminal window and navigate to the src/ontology directory ( cd cl/src/ontology ) Run release pipeline: sh run.sh make prepare_release -B . Note that for some ontologies, this process can take up to 90 minutes - especially if there are large ontologies you depend on, like PRO or CHEBI. If everything went well, you should see the following output on your machine: Release files are now in ../.. - now you should commit, push and make a release on your git hosting site such as GitHub or GitLab . This will create all the specified release targets (OBO, OWL, JSON, and the variants, ont-full and ont-base) and copy them into your release directory (the top level of your repo).","title":"Run a release with the ODK"},{"location":"odk-workflows/ReleaseWorkflow/#review-the-release","text":"(Optional) Rough check. This step is frequently skipped, but for the more paranoid among us (like the author of this doc), this is a 3 minute additional effort for some peace of mind. Open the main release (cl.owl) in you favourite development environment (i.e. Prot\u00e9g\u00e9) and eyeball the hierarchy. We recommend two simple checks: Does the very top level of the hierarchy look ok? This means that all new terms have been imported/updated correctly. Does at least one change that you know should be in this release appear? For example, a new class. This means that the release was actually based on the recent edit file. Commit your changes to the branch and make a pull request In your GitHub pull request, review the following three files in detail (based on our experience): cl.obo - this reflects a useful subset of the whole ontology (everything that can be covered by OBO format). OBO format has that speaking for it: it is very easy to review! cl-base.owl - this reflects the asserted axioms in your ontology that you have actually edited. Ideally also take a look at cl-full.owl , which may reveal interesting new inferences you did not know about. Note that the diff of this file is sometimes quite large. Like with every pull request, we recommend to always employ a second set of eyes when reviewing a PR!","title":"Review the release"},{"location":"odk-workflows/ReleaseWorkflow/#merge-the-main-branch","text":"Once your CI checks have passed, and your reviews are completed, you can now merge the branch into your main branch (don't forget to delete the branch afterwards - a big button will appear after the merge is finished).","title":"Merge the main branch"},{"location":"odk-workflows/ReleaseWorkflow/#create-a-github-release","text":"Go to your releases page on GitHub by navigating to your repository, and then clicking on releases (usually on the right, for example: https://github.com/obophenotype/cell-ontology/releases). Then click \"Draft new release\" As the tag version you need to choose the date on which your ontologies were build. You can find this, for example, by looking at the cl.obo file and check the data-version: property. The date needs to be prefixed with a v , so, for example v2020-02-06 . You can write whatever you want in the release title, but we typically write the date again. The description underneath should contain a concise list of changes or term additions. Click \"Publish release\". Done.","title":"Create a GitHub release"},{"location":"odk-workflows/ReleaseWorkflow/#debugging-typical-ontology-release-problems","text":"","title":"Debugging typical ontology release problems"},{"location":"odk-workflows/ReleaseWorkflow/#problems-with-memory","text":"When you are dealing with large ontologies, you need a lot of memory. When you see error messages relating to large ontologies such as CHEBI, PRO, NCBITAXON, or Uberon, you should think of memory first, see here .","title":"Problems with memory"},{"location":"odk-workflows/ReleaseWorkflow/#problems-when-using-obo-format-based-tools","text":"Sometimes you will get cryptic error messages when using legacy tools using OBO format, such as the ontology release tool (OORT), which is also available as part of the ODK docker container. In these cases, you need to track down what axiom or annotation actually caused the breakdown. In our experience (in about 60% of the cases) the problem lies with duplicate annotations ( def , comment ) which are illegal in OBO. Here is an example recipe of how to deal with such a problem: If you get a message like make: *** [cl.Makefile:84: oort] Error 255 you might have a OORT error. To debug this, in your terminal enter sh run.sh make IMP=false PAT=false oort -B (assuming you are already in the ontology folder in your directory) This should show you where the error is in the log (eg multiple different definitions) WARNING: THE FIX BELOW IS NOT IDEAL, YOU SHOULD ALWAYS TRY TO FIX UPSTREAM IF POSSIBLE Open cl-edit.owl in Prot\u00e9g\u00e9 and find the offending term and delete all offending issue (e.g. delete ALL definition, if the problem was \"multiple def tags not allowed\") and save. *While this is not idea, as it will remove all definitions from that term, it will be added back again when the term is fixed in the ontology it was imported from and added back in. Rerun sh run.sh make IMP=false PAT=false oort -B and if it all passes, commit your changes to a branch and make a pull request as usual.","title":"Problems when using OBO format based tools"},{"location":"odk-workflows/RepoManagement/","text":"Managing your ODK repository \u00b6 Updating your ODK repository \u00b6 Your ODK repositories configuration is managed in src/ontology/cl-odk.yaml . The ODK Project Configuration Schema defines all possible parameters that can be used in this config YAML. Once you have made your changes, you can run the following to apply your changes to the repository: sh run.sh make update_repo There are a large number of options that can be set to configure your ODK, but we will only discuss a few of them here. NOTE for Windows users: You may get a cryptic failure such as Set Illegal Option - if the update script located in src/scripts/update_repo.sh was saved using Windows Line endings. These need to change to unix line endings. In Notepad++, for example, you can click on Edit->EOL Conversion->Unix LF to change this. Managing imports \u00b6 You can use the update repository workflow described on this page to perform the following operations to your imports: Add a new import Modify an existing import Remove an import you no longer want Customise an import We will discuss all these workflows in the following. Add new import \u00b6 To add a new import, you first edit your odk config as described above , adding an id to the product list in the import_group section (for the sake of this example, we assume you already import RO, and your goal is to also import GO): import_group: products: - id: ro - id: go Note: our ODK file should only have one import_group which can contain multiple imports (in the products section). Next, you run the update repo workflow to apply these changes. Note that by default, this module is going to be a SLME Bottom module, see here . To change that or customise your module, see section \"Customise an import\". To finalise the addition of your import, perform the following steps: Add an import statement to your src/ontology/cl-edit.owl file. We suggest to do this using a text editor, by simply copying an existing import declaration and renaming it to the new ontology import, for example as follows: ... Ontology( Import() Import() ... Add your imports redirect to your catalog file src/ontology/catalog-v001.xml , for example: Test whether everything is in order: Refresh your import Open in your Ontology Editor of choice (Protege) and ensure that the expected terms are imported. Note: The catalog file src/ontology/catalog-v001.xml has one purpose: redirecting imports from URLs to local files. For example, if you have Import() in your editors file (the ontology) and in your catalog, tools like robot or Prot\u00e9g\u00e9 will recognize the statement in the catalog file to redirect the URL http://purl.obolibrary.org/obo/cl/imports/go_import.owl to the local file imports/go_import.owl (which is in your src/ontology directory). Modify an existing import \u00b6 If you simply wish to refresh your import in light of new terms, see here . If you wish to change the type of your module see section \"Customise an import\". Remove an existing import \u00b6 To remove an existing import, perform the following steps: remove the import declaration from your src/ontology/cl-edit.owl . remove the id from your src/ontology/cl-odk.yaml , eg. - id: go from the list of products in the import_group . run update repo workflow delete the associated files manually: src/imports/go_import.owl src/imports/go_terms.txt Remove the respective entry from the src/ontology/catalog-v001.xml file. Customise an import \u00b6 By default, an import module extracted from a source ontology will be a SLME module, see here . There are various options to change the default. The following change to your repo config ( src/ontology/cl-odk.yaml ) will switch the go import from an SLME module to a simple ROBOT filter module: import_group: products: - id: ro - id: go module_type: filter A ROBOT filter module is, essentially, importing all external terms declared by your ontology (see here on how to declare external terms to be imported). Note that the filter module does not consider terms/annotations from namespaces other than the base-namespace of the ontology itself. For example, in the example of GO above, only annotations / axioms related to the GO base IRI (http://purl.obolibrary.org/obo/GO_) would be considered. This behaviour can be changed by adding additional base IRIs as follows: import_group: products: - id: go module_type: filter base_iris: - http://purl.obolibrary.org/obo/GO_ - http://purl.obolibrary.org/obo/CL_ - http://purl.obolibrary.org/obo/BFO If you wish to customise your import entirely, you can specify your own ROBOT command to do so. To do that, add the following to your repo config ( src/ontology/cl-odk.yaml ): import_group: products: - id: ro - id: go module_type: custom Now add a new goal in your custom Makefile ( src/ontology/cl.Makefile , not src/ontology/Makefile ). imports/go_import.owl: mirror/ro.owl imports/ro_terms_combined.txt if [ $(IMP) = true ]; then $(ROBOT) query -i $< --update ../sparql/preprocess-module.ru \\ extract -T imports/ro_terms_combined.txt --force true --individuals exclude --method BOT \\ query --update ../sparql/inject-subset-declaration.ru --update ../sparql/postprocess-module.ru \\ annotate --ontology-iri $(ONTBASE)/$@ $(ANNOTATE_ONTOLOGY_VERSION) --output $@.tmp.owl && mv $@.tmp.owl $@; fi Now feel free to change this goal to do whatever you wish it to do! It probably makes some sense (albeit not being a strict necessity), to leave most of the goal instead and replace only: extract -T imports/ro_terms_combined.txt --force true --individuals exclude --method BOT \\ to another ROBOT pipeline. Add a component \u00b6 A component is an import which belongs to your ontology, e.g. is managed by you and your team. Open src/ontology/cl-odk.yaml If you dont have it yet, add a new top level section components Under the components section, add a new section called products . This is where all your components are specified Under the products section, add a new component, e.g. - filename: mycomp.owl Example components: products: - filename: mycomp.owl When running sh run.sh make update_repo , a new file src/ontology/components/mycomp.owl will be created which you can edit as you see fit. Typical ways to edit: Using a ROBOT template to generate the component (see below) Manually curating the component separately with Prot\u00e9g\u00e9 or any other editor Providing a components/mycomp.owl: make target in src/ontology/cl.Makefile and provide a custom command to generate the component WARNING : Note that the custom rule to generate the component MUST NOT depend on any other ODK-generated file such as seed files and the like (see issue ). Providing an additional attribute for the component in src/ontology/cl-odk.yaml , source , to specify that this component should simply be downloaded from somewhere on the web. Adding a new component based on a ROBOT template \u00b6 Since ODK 1.3.2, it is possible to simply link a ROBOT template to a component without having to specify any of the import logic. In order to add a new component that is connected to one or more template files, follow these steps: Open src/ontology/cl-odk.yaml . Make sure that use_templates: TRUE is set in the global project options. You should also make sure that use_context: TRUE is set in case you are using prefixes in your templates that are not known to robot , such as OMOP: , CPONT: and more. All non-standard prefixes you are using should be added to config/context.json . Add another component to the products section. To activate this component to be template-driven, simply say: use_template: TRUE . This will create an empty template for you in the templates directory, which will automatically be processed when recreating the component (e.g. run.bat make recreate-mycomp ). If you want to use more than one component, use the templates field to add as many template names as you wish. ODK will look for them in the src/templates directory. Advanced: If you want to provide additional processing options, you can use the template_options field. This should be a string with option from robot template . One typical example for additional options you may want to provide is --add-prefixes config/context.json to ensure the prefix map of your context is provided to robot , see above. Example : components: products: - filename: mycomp.owl use_template: TRUE template_options: --add-prefixes config/context.json templates: - template1.tsv - template2.tsv Note : if your mirror is particularly large and complex, read this ODK recommendation .","title":"Manage your ODK Repository"},{"location":"odk-workflows/RepoManagement/#managing-your-odk-repository","text":"","title":"Managing your ODK repository"},{"location":"odk-workflows/RepoManagement/#updating-your-odk-repository","text":"Your ODK repositories configuration is managed in src/ontology/cl-odk.yaml . The ODK Project Configuration Schema defines all possible parameters that can be used in this config YAML. Once you have made your changes, you can run the following to apply your changes to the repository: sh run.sh make update_repo There are a large number of options that can be set to configure your ODK, but we will only discuss a few of them here. NOTE for Windows users: You may get a cryptic failure such as Set Illegal Option - if the update script located in src/scripts/update_repo.sh was saved using Windows Line endings. These need to change to unix line endings. In Notepad++, for example, you can click on Edit->EOL Conversion->Unix LF to change this.","title":"Updating your ODK repository"},{"location":"odk-workflows/RepoManagement/#managing-imports","text":"You can use the update repository workflow described on this page to perform the following operations to your imports: Add a new import Modify an existing import Remove an import you no longer want Customise an import We will discuss all these workflows in the following.","title":"Managing imports"},{"location":"odk-workflows/RepoManagement/#add-new-import","text":"To add a new import, you first edit your odk config as described above , adding an id to the product list in the import_group section (for the sake of this example, we assume you already import RO, and your goal is to also import GO): import_group: products: - id: ro - id: go Note: our ODK file should only have one import_group which can contain multiple imports (in the products section). Next, you run the update repo workflow to apply these changes. Note that by default, this module is going to be a SLME Bottom module, see here . To change that or customise your module, see section \"Customise an import\". To finalise the addition of your import, perform the following steps: Add an import statement to your src/ontology/cl-edit.owl file. We suggest to do this using a text editor, by simply copying an existing import declaration and renaming it to the new ontology import, for example as follows: ... Ontology( Import() Import() ... Add your imports redirect to your catalog file src/ontology/catalog-v001.xml , for example: Test whether everything is in order: Refresh your import Open in your Ontology Editor of choice (Protege) and ensure that the expected terms are imported. Note: The catalog file src/ontology/catalog-v001.xml has one purpose: redirecting imports from URLs to local files. For example, if you have Import() in your editors file (the ontology) and in your catalog, tools like robot or Prot\u00e9g\u00e9 will recognize the statement in the catalog file to redirect the URL http://purl.obolibrary.org/obo/cl/imports/go_import.owl to the local file imports/go_import.owl (which is in your src/ontology directory).","title":"Add new import"},{"location":"odk-workflows/RepoManagement/#modify-an-existing-import","text":"If you simply wish to refresh your import in light of new terms, see here . If you wish to change the type of your module see section \"Customise an import\".","title":"Modify an existing import"},{"location":"odk-workflows/RepoManagement/#remove-an-existing-import","text":"To remove an existing import, perform the following steps: remove the import declaration from your src/ontology/cl-edit.owl . remove the id from your src/ontology/cl-odk.yaml , eg. - id: go from the list of products in the import_group . run update repo workflow delete the associated files manually: src/imports/go_import.owl src/imports/go_terms.txt Remove the respective entry from the src/ontology/catalog-v001.xml file.","title":"Remove an existing import"},{"location":"odk-workflows/RepoManagement/#customise-an-import","text":"By default, an import module extracted from a source ontology will be a SLME module, see here . There are various options to change the default. The following change to your repo config ( src/ontology/cl-odk.yaml ) will switch the go import from an SLME module to a simple ROBOT filter module: import_group: products: - id: ro - id: go module_type: filter A ROBOT filter module is, essentially, importing all external terms declared by your ontology (see here on how to declare external terms to be imported). Note that the filter module does not consider terms/annotations from namespaces other than the base-namespace of the ontology itself. For example, in the example of GO above, only annotations / axioms related to the GO base IRI (http://purl.obolibrary.org/obo/GO_) would be considered. This behaviour can be changed by adding additional base IRIs as follows: import_group: products: - id: go module_type: filter base_iris: - http://purl.obolibrary.org/obo/GO_ - http://purl.obolibrary.org/obo/CL_ - http://purl.obolibrary.org/obo/BFO If you wish to customise your import entirely, you can specify your own ROBOT command to do so. To do that, add the following to your repo config ( src/ontology/cl-odk.yaml ): import_group: products: - id: ro - id: go module_type: custom Now add a new goal in your custom Makefile ( src/ontology/cl.Makefile , not src/ontology/Makefile ). imports/go_import.owl: mirror/ro.owl imports/ro_terms_combined.txt if [ $(IMP) = true ]; then $(ROBOT) query -i $< --update ../sparql/preprocess-module.ru \\ extract -T imports/ro_terms_combined.txt --force true --individuals exclude --method BOT \\ query --update ../sparql/inject-subset-declaration.ru --update ../sparql/postprocess-module.ru \\ annotate --ontology-iri $(ONTBASE)/$@ $(ANNOTATE_ONTOLOGY_VERSION) --output $@.tmp.owl && mv $@.tmp.owl $@; fi Now feel free to change this goal to do whatever you wish it to do! It probably makes some sense (albeit not being a strict necessity), to leave most of the goal instead and replace only: extract -T imports/ro_terms_combined.txt --force true --individuals exclude --method BOT \\ to another ROBOT pipeline.","title":"Customise an import"},{"location":"odk-workflows/RepoManagement/#add-a-component","text":"A component is an import which belongs to your ontology, e.g. is managed by you and your team. Open src/ontology/cl-odk.yaml If you dont have it yet, add a new top level section components Under the components section, add a new section called products . This is where all your components are specified Under the products section, add a new component, e.g. - filename: mycomp.owl Example components: products: - filename: mycomp.owl When running sh run.sh make update_repo , a new file src/ontology/components/mycomp.owl will be created which you can edit as you see fit. Typical ways to edit: Using a ROBOT template to generate the component (see below) Manually curating the component separately with Prot\u00e9g\u00e9 or any other editor Providing a components/mycomp.owl: make target in src/ontology/cl.Makefile and provide a custom command to generate the component WARNING : Note that the custom rule to generate the component MUST NOT depend on any other ODK-generated file such as seed files and the like (see issue ). Providing an additional attribute for the component in src/ontology/cl-odk.yaml , source , to specify that this component should simply be downloaded from somewhere on the web.","title":"Add a component"},{"location":"odk-workflows/RepoManagement/#adding-a-new-component-based-on-a-robot-template","text":"Since ODK 1.3.2, it is possible to simply link a ROBOT template to a component without having to specify any of the import logic. In order to add a new component that is connected to one or more template files, follow these steps: Open src/ontology/cl-odk.yaml . Make sure that use_templates: TRUE is set in the global project options. You should also make sure that use_context: TRUE is set in case you are using prefixes in your templates that are not known to robot , such as OMOP: , CPONT: and more. All non-standard prefixes you are using should be added to config/context.json . Add another component to the products section. To activate this component to be template-driven, simply say: use_template: TRUE . This will create an empty template for you in the templates directory, which will automatically be processed when recreating the component (e.g. run.bat make recreate-mycomp ). If you want to use more than one component, use the templates field to add as many template names as you wish. ODK will look for them in the src/templates directory. Advanced: If you want to provide additional processing options, you can use the template_options field. This should be a string with option from robot template . One typical example for additional options you may want to provide is --add-prefixes config/context.json to ensure the prefix map of your context is provided to robot , see above. Example : components: products: - filename: mycomp.owl use_template: TRUE template_options: --add-prefixes config/context.json templates: - template1.tsv - template2.tsv Note : if your mirror is particularly large and complex, read this ODK recommendation .","title":"Adding a new component based on a ROBOT template"},{"location":"odk-workflows/RepositoryFileStructure/","text":"Repository structure \u00b6 The main kinds of files in the repository: Release files Imports Components Release files \u00b6 Release file are the file that are considered part of the official ontology release and to be used by the community. A detailed description of the release artefacts can be found here . Imports \u00b6 Imports are subsets of external ontologies that contain terms and axioms you would like to re-use in your ontology. These are considered \"external\", like dependencies in software development, and are not included in your \"base\" product, which is the release artefact which contains only those axioms that you personally maintain. These are the current imports in CL Import URL Type pr https://raw.githubusercontent.com/obophenotype/pro_obo_slim/master/pr_slim.owl None go http://purl.obolibrary.org/obo/go.owl None uberon http://purl.obolibrary.org/obo/uberon.owl None ro http://purl.obolibrary.org/obo/ro.owl None pato http://purl.obolibrary.org/obo/pato.owl None ncbitaxon http://purl.obolibrary.org/obo/ncbitaxon/subsets/taxslim.owl None ncbitaxondisjoints http://purl.obolibrary.org/obo/ncbitaxon/subsets/taxslim-disjoint-over-in-taxon.owl None omo http://purl.obolibrary.org/obo/omo.owl mirror Components \u00b6 Components, in contrast to imports, are considered full members of the ontology. This means that any axiom in a component is also included in the ontology base - which means it is considered native to the ontology. While this sounds complicated, consider this: conceptually, no component should be part of more than one ontology. If that seems to be the case, we are most likely talking about an import. Components are often not needed for ontologies, but there are some use cases: There is an automated process that generates and re-generates a part of the ontology A part of the ontology is managed in ROBOT templates The expressivity of the component is higher than the format of the edit file. For example, people still choose to manage their ontology in OBO format (they should not) missing out on a lot of owl features. They may choose to manage logic that is beyond OBO in a specific OWL component. These are the components in CL Filename URL hra_subset.owl None mappings.owl None blood_and_immune_upper_slim.owl None eye_upper_slim.owl None general_cell_types_upper_slim.owl None kidney_upper_slim.owl None cellxgene_subset.owl None clm-cl.owl None","title":"Your ODK Repository Overview"},{"location":"odk-workflows/RepositoryFileStructure/#repository-structure","text":"The main kinds of files in the repository: Release files Imports Components","title":"Repository structure"},{"location":"odk-workflows/RepositoryFileStructure/#release-files","text":"Release file are the file that are considered part of the official ontology release and to be used by the community. A detailed description of the release artefacts can be found here .","title":"Release files"},{"location":"odk-workflows/RepositoryFileStructure/#imports","text":"Imports are subsets of external ontologies that contain terms and axioms you would like to re-use in your ontology. These are considered \"external\", like dependencies in software development, and are not included in your \"base\" product, which is the release artefact which contains only those axioms that you personally maintain. These are the current imports in CL Import URL Type pr https://raw.githubusercontent.com/obophenotype/pro_obo_slim/master/pr_slim.owl None go http://purl.obolibrary.org/obo/go.owl None uberon http://purl.obolibrary.org/obo/uberon.owl None ro http://purl.obolibrary.org/obo/ro.owl None pato http://purl.obolibrary.org/obo/pato.owl None ncbitaxon http://purl.obolibrary.org/obo/ncbitaxon/subsets/taxslim.owl None ncbitaxondisjoints http://purl.obolibrary.org/obo/ncbitaxon/subsets/taxslim-disjoint-over-in-taxon.owl None omo http://purl.obolibrary.org/obo/omo.owl mirror","title":"Imports"},{"location":"odk-workflows/RepositoryFileStructure/#components","text":"Components, in contrast to imports, are considered full members of the ontology. This means that any axiom in a component is also included in the ontology base - which means it is considered native to the ontology. While this sounds complicated, consider this: conceptually, no component should be part of more than one ontology. If that seems to be the case, we are most likely talking about an import. Components are often not needed for ontologies, but there are some use cases: There is an automated process that generates and re-generates a part of the ontology A part of the ontology is managed in ROBOT templates The expressivity of the component is higher than the format of the edit file. For example, people still choose to manage their ontology in OBO format (they should not) missing out on a lot of owl features. They may choose to manage logic that is beyond OBO in a specific OWL component. These are the components in CL Filename URL hra_subset.owl None mappings.owl None blood_and_immune_upper_slim.owl None eye_upper_slim.owl None general_cell_types_upper_slim.owl None kidney_upper_slim.owl None cellxgene_subset.owl None clm-cl.owl None","title":"Components"},{"location":"odk-workflows/SettingUpDockerForODK/","text":"Setting up your Docker environment for ODK use \u00b6 One of the most frequent problems with running the ODK for the first time is failure because of lack of memory. This can look like a Java OutOfMemory exception, but more often than not it will appear as something like an Error 137 . There are two places you need to consider to set your memory: Your src/ontology/run.sh (or run.bat) file. You can set the memory in there by adding robot_java_args: '-Xmx8G' to your src/ontology/cl-odk.yaml file, see for example here . Set your docker memory. By default, it should be about 10-20% more than your robot_java_args variable. You can manage your memory settings by right-clicking on the docker whale in your system bar-->Preferences-->Resources-->Advanced, see picture below.","title":"Setting up Docker for ODK"},{"location":"odk-workflows/SettingUpDockerForODK/#setting-up-your-docker-environment-for-odk-use","text":"One of the most frequent problems with running the ODK for the first time is failure because of lack of memory. This can look like a Java OutOfMemory exception, but more often than not it will appear as something like an Error 137 . There are two places you need to consider to set your memory: Your src/ontology/run.sh (or run.bat) file. You can set the memory in there by adding robot_java_args: '-Xmx8G' to your src/ontology/cl-odk.yaml file, see for example here . Set your docker memory. By default, it should be about 10-20% more than your robot_java_args variable. You can manage your memory settings by right-clicking on the docker whale in your system bar-->Preferences-->Resources-->Advanced, see picture below.","title":"Setting up your Docker environment for ODK use"},{"location":"odk-workflows/UpdateImports/","text":"Update Imports Workflow \u00b6 This page discusses how to update the contents of your imports, like adding or removing terms. If you are looking to customise imports, like changing the module type, see here . Importing a new term \u00b6 Note: some ontologies now use a merged-import system to manage dynamic imports, for these please follow instructions in the section title \"Using the Base Module approach\". Importing a new term is split into two sub-phases: Declaring the terms to be imported Refreshing imports dynamically Declaring terms to be imported \u00b6 There are three ways to declare terms that are to be imported from an external ontology. Choose the appropriate one for your particular scenario (all three can be used in parallel if need be): Prot\u00e9g\u00e9-based declaration Using term files Using the custom import template Prot\u00e9g\u00e9-based declaration \u00b6 This workflow is to be avoided, but may be appropriate if the editor does not have access to the ODK docker container . This approach also applies to ontologies that use base module import approach. Open your ontology (edit file) in Prot\u00e9g\u00e9 (5.5+). Select 'owl:Thing' Add a new class as usual. Paste the full iri in the 'Name:' field, for example, http://purl.obolibrary.org/obo/CHEBI_50906. Click 'OK' Now you can use this term for example to construct logical definitions. The next time the imports are refreshed (see how to refresh here ), the metadata (labels, definitions, etc.) for this term are imported from the respective external source ontology and becomes visible in your ontology. Using term files \u00b6 Every import has, by default a term file associated with it, which can be found in the imports directory. For example, if you have a GO import in src/ontology/go_import.owl , you will also have an associated term file src/ontology/go_terms.txt . You can add terms in there simply as a list: GO:0008150 GO:0008151 Now you can run the refresh imports workflow ) and the two terms will be imported. Using the custom import template \u00b6 This workflow is appropriate if: You prefer to manage all your imported terms in a single file (rather than multiple files like in the \"Using term files\" workflow above). You wish to augment your imported ontologies with additional information. This requires a cautionary discussion. To enable this workflow, you add the following to your ODK config file ( src/ontology/cl-odk.yaml ), and update the repository : use_custom_import_module: TRUE Now you can manage your imported terms directly in the custom external terms template, which is located at src/templates/external_import.owl . Note that this file is a ROBOT template , and can, in principle, be extended to include any axioms you like. Before extending the template, however, read the following carefully. The main purpose of the custom import template is to enable the management off all terms to be imported in a centralised place. To enable that, you do not have to do anything other than maintaining the template. So if you, say currently import APOLLO_SV:00000480 , and you wish to import APOLLO_SV:00000532 , you simply add a row like this: ID Entity Type ID TYPE APOLLO_SV:00000480 owl:Class APOLLO_SV:00000532 owl:Class When the imports are refreshed see imports refresh workflow , the term(s) will simply be imported from the configured ontologies. Now, if you wish to extend the Makefile (which is beyond these instructions) and add, say, synonyms to the imported terms, you can do that, but you need to (a) preserve the ID and ENTITY columns and (b) ensure that the ROBOT template is valid otherwise, see here . WARNING . Note that doing this is a widespread antipattern (see related issue ). You should not change the axioms of terms that do not belong into your ontology unless necessary - such changes should always be pushed into the ontology where they belong. However, since people are doing it, whether the OBO Foundry likes it or not, at least using the custom imports module as described here localises the changes to a single simple template and ensures that none of the annotations added this way are merged into the base file . Refresh imports \u00b6 If you want to refresh the import yourself (this may be necessary to pass the travis tests), and you have the ODK installed, you can do the following (using go as an example): First, you navigate in your terminal to the ontology directory (underneath src in your hpo root directory). cd src/ontology Then, you regenerate the import that will now include any new terms you have added. Note: You must have docker installed . sh run.sh make PAT=false imports/go_import.owl -B Since ODK 1.2.27, it is also possible to simply run the following, which is the same as the above: sh run.sh make refresh-go Note that in case you changed the defaults, you need to add IMP=true and/or MIR=true to the command below: sh run.sh make IMP=true MIR=true PAT=false imports/go_import.owl -B If you wish to skip refreshing the mirror, i.e. skip downloading the latest version of the source ontology for your import (e.g. go.owl for your go import) you can set MIR=false instead, which will do the exact same thing as the above, but is easier to remember: sh run.sh make IMP=true MIR=false PAT=false imports/go_import.owl -B Using the Base Module approach \u00b6 Since ODK 1.2.31, we support an entirely new approach to generate modules: Using base files. The idea is to only import axioms from ontologies that actually belong to it . A base file is a subset of the ontology that only contains those axioms that nominally belong there. In other words, the base file does not contain any axioms that belong to another ontology. An example would be this: Imagine this being the full Uberon ontology: Axiom 1: BFO:123 SubClassOf BFO:124 Axiom 1: UBERON:123 SubClassOf BFO:123 Axiom 1: UBERON:124 SubClassOf UBERON 123 The base file is the set of all axioms that are about UBERON terms: Axiom 1: UBERON:123 SubClassOf BFO:123 Axiom 1: UBERON:124 SubClassOf UBERON 123 I.e. Axiom 1: BFO:123 SubClassOf BFO:124 Gets removed. The base file pipeline is a bit more complex than the normal pipelines, because of the logical interactions between the imported ontologies. This is solved by _first merging all mirrors into one huge file and then extracting one mega module from it. Example: Let's say we are importing terms from Uberon, GO and RO in our ontologies. When we use the base pipelines, we 1) First obtain the base (usually by simply downloading it, but there is also an option now to create it with ROBOT) 2) We merge all base files into one big pile 3) Then we extract a single module imports/merged_import.owl The first implementation of this pipeline is PATO, see https://github.com/pato-ontology/pato/blob/master/src/ontology/pato-odk.yaml. To check if your ontology uses this method, check src/ontology/cl-odk.yaml to see if use_base_merging: TRUE is declared under import_group If your ontology uses Base Module approach, please use the following steps: First, add the term to be imported to the term file associated with it (see above \"Using term files\" section if this is not clear to you) Next, you navigate in your terminal to the ontology directory (underneath src in your hpo root directory). cd src/ontology Then refresh imports by running sh run.sh make imports/merged_import.owl Note: if your mirrors are updated, you can run sh run.sh make no-mirror-refresh-merged This requires quite a bit of memory on your local machine, so if you encounter an error, it might be a lack of memory on your computer. A solution would be to create a ticket in an issue tracker requesting for the term to be imported, and one of the local devs should pick this up and run the import for you. Lastly, restart Prot\u00e9g\u00e9, and the term should be imported in ready to be used.","title":"Imports management"},{"location":"odk-workflows/UpdateImports/#update-imports-workflow","text":"This page discusses how to update the contents of your imports, like adding or removing terms. If you are looking to customise imports, like changing the module type, see here .","title":"Update Imports Workflow"},{"location":"odk-workflows/UpdateImports/#importing-a-new-term","text":"Note: some ontologies now use a merged-import system to manage dynamic imports, for these please follow instructions in the section title \"Using the Base Module approach\". Importing a new term is split into two sub-phases: Declaring the terms to be imported Refreshing imports dynamically","title":"Importing a new term"},{"location":"odk-workflows/UpdateImports/#declaring-terms-to-be-imported","text":"There are three ways to declare terms that are to be imported from an external ontology. Choose the appropriate one for your particular scenario (all three can be used in parallel if need be): Prot\u00e9g\u00e9-based declaration Using term files Using the custom import template","title":"Declaring terms to be imported"},{"location":"odk-workflows/UpdateImports/#protege-based-declaration","text":"This workflow is to be avoided, but may be appropriate if the editor does not have access to the ODK docker container . This approach also applies to ontologies that use base module import approach. Open your ontology (edit file) in Prot\u00e9g\u00e9 (5.5+). Select 'owl:Thing' Add a new class as usual. Paste the full iri in the 'Name:' field, for example, http://purl.obolibrary.org/obo/CHEBI_50906. Click 'OK' Now you can use this term for example to construct logical definitions. The next time the imports are refreshed (see how to refresh here ), the metadata (labels, definitions, etc.) for this term are imported from the respective external source ontology and becomes visible in your ontology.","title":"Prot\u00e9g\u00e9-based declaration"},{"location":"odk-workflows/UpdateImports/#using-term-files","text":"Every import has, by default a term file associated with it, which can be found in the imports directory. For example, if you have a GO import in src/ontology/go_import.owl , you will also have an associated term file src/ontology/go_terms.txt . You can add terms in there simply as a list: GO:0008150 GO:0008151 Now you can run the refresh imports workflow ) and the two terms will be imported.","title":"Using term files"},{"location":"odk-workflows/UpdateImports/#using-the-custom-import-template","text":"This workflow is appropriate if: You prefer to manage all your imported terms in a single file (rather than multiple files like in the \"Using term files\" workflow above). You wish to augment your imported ontologies with additional information. This requires a cautionary discussion. To enable this workflow, you add the following to your ODK config file ( src/ontology/cl-odk.yaml ), and update the repository : use_custom_import_module: TRUE Now you can manage your imported terms directly in the custom external terms template, which is located at src/templates/external_import.owl . Note that this file is a ROBOT template , and can, in principle, be extended to include any axioms you like. Before extending the template, however, read the following carefully. The main purpose of the custom import template is to enable the management off all terms to be imported in a centralised place. To enable that, you do not have to do anything other than maintaining the template. So if you, say currently import APOLLO_SV:00000480 , and you wish to import APOLLO_SV:00000532 , you simply add a row like this: ID Entity Type ID TYPE APOLLO_SV:00000480 owl:Class APOLLO_SV:00000532 owl:Class When the imports are refreshed see imports refresh workflow , the term(s) will simply be imported from the configured ontologies. Now, if you wish to extend the Makefile (which is beyond these instructions) and add, say, synonyms to the imported terms, you can do that, but you need to (a) preserve the ID and ENTITY columns and (b) ensure that the ROBOT template is valid otherwise, see here . WARNING . Note that doing this is a widespread antipattern (see related issue ). You should not change the axioms of terms that do not belong into your ontology unless necessary - such changes should always be pushed into the ontology where they belong. However, since people are doing it, whether the OBO Foundry likes it or not, at least using the custom imports module as described here localises the changes to a single simple template and ensures that none of the annotations added this way are merged into the base file .","title":"Using the custom import template"},{"location":"odk-workflows/UpdateImports/#refresh-imports","text":"If you want to refresh the import yourself (this may be necessary to pass the travis tests), and you have the ODK installed, you can do the following (using go as an example): First, you navigate in your terminal to the ontology directory (underneath src in your hpo root directory). cd src/ontology Then, you regenerate the import that will now include any new terms you have added. Note: You must have docker installed . sh run.sh make PAT=false imports/go_import.owl -B Since ODK 1.2.27, it is also possible to simply run the following, which is the same as the above: sh run.sh make refresh-go Note that in case you changed the defaults, you need to add IMP=true and/or MIR=true to the command below: sh run.sh make IMP=true MIR=true PAT=false imports/go_import.owl -B If you wish to skip refreshing the mirror, i.e. skip downloading the latest version of the source ontology for your import (e.g. go.owl for your go import) you can set MIR=false instead, which will do the exact same thing as the above, but is easier to remember: sh run.sh make IMP=true MIR=false PAT=false imports/go_import.owl -B","title":"Refresh imports"},{"location":"odk-workflows/UpdateImports/#using-the-base-module-approach","text":"Since ODK 1.2.31, we support an entirely new approach to generate modules: Using base files. The idea is to only import axioms from ontologies that actually belong to it . A base file is a subset of the ontology that only contains those axioms that nominally belong there. In other words, the base file does not contain any axioms that belong to another ontology. An example would be this: Imagine this being the full Uberon ontology: Axiom 1: BFO:123 SubClassOf BFO:124 Axiom 1: UBERON:123 SubClassOf BFO:123 Axiom 1: UBERON:124 SubClassOf UBERON 123 The base file is the set of all axioms that are about UBERON terms: Axiom 1: UBERON:123 SubClassOf BFO:123 Axiom 1: UBERON:124 SubClassOf UBERON 123 I.e. Axiom 1: BFO:123 SubClassOf BFO:124 Gets removed. The base file pipeline is a bit more complex than the normal pipelines, because of the logical interactions between the imported ontologies. This is solved by _first merging all mirrors into one huge file and then extracting one mega module from it. Example: Let's say we are importing terms from Uberon, GO and RO in our ontologies. When we use the base pipelines, we 1) First obtain the base (usually by simply downloading it, but there is also an option now to create it with ROBOT) 2) We merge all base files into one big pile 3) Then we extract a single module imports/merged_import.owl The first implementation of this pipeline is PATO, see https://github.com/pato-ontology/pato/blob/master/src/ontology/pato-odk.yaml. To check if your ontology uses this method, check src/ontology/cl-odk.yaml to see if use_base_merging: TRUE is declared under import_group If your ontology uses Base Module approach, please use the following steps: First, add the term to be imported to the term file associated with it (see above \"Using term files\" section if this is not clear to you) Next, you navigate in your terminal to the ontology directory (underneath src in your hpo root directory). cd src/ontology Then refresh imports by running sh run.sh make imports/merged_import.owl Note: if your mirrors are updated, you can run sh run.sh make no-mirror-refresh-merged This requires quite a bit of memory on your local machine, so if you encounter an error, it might be a lack of memory on your computer. A solution would be to create a ticket in an issue tracker requesting for the term to be imported, and one of the local devs should pick this up and run the import for you. Lastly, restart Prot\u00e9g\u00e9, and the term should be imported in ready to be used.","title":"Using the Base Module approach"},{"location":"odk-workflows/components/","text":"Adding components to an ODK repo \u00b6 For details on what components are, please see component section of repository file structure document . To add custom components to an ODK repo, please follow the following steps: 1) Locate your odk yaml file and open it with your favourite text editor (src/ontology/cl-odk.yaml) 2) Search if there is already a component section to the yaml file, if not add it accordingly, adding the name of your component: components: products: - filename: your-component-name.owl 3) Add the component to your catalog file (src/ontology/catalog-v001.xml) 4) Add the component to the edit file (src/ontology/cl-edit.obo) for .obo formats: import: http://purl.obolibrary.org/obo/cl/components/your-component-name.owl for .owl formats: Import() 5) Refresh your repo by running sh run.sh make update_repo - this should create a new file in src/ontology/components. 6) In your custom makefile (src/ontology/cl.Makefile) add a goal for your custom make file. In this example, the goal is a ROBOT template. $(COMPONENTSDIR)/your-component-name.owl: $(SRC) ../templates/your-component-template.tsv $(ROBOT) template --template ../templates/your-component-template.tsv \\ annotate --ontology-iri $(ONTBASE)/$@ --output $(COMPONENTSDIR)/your-component-name.owl (If using a ROBOT template, do not forget to add your template tsv in src/templates/) 7) Make the file by running sh run.sh make components/your-component-name.owl","title":"Adding components to an ODK repo"},{"location":"odk-workflows/components/#adding-components-to-an-odk-repo","text":"For details on what components are, please see component section of repository file structure document . To add custom components to an ODK repo, please follow the following steps: 1) Locate your odk yaml file and open it with your favourite text editor (src/ontology/cl-odk.yaml) 2) Search if there is already a component section to the yaml file, if not add it accordingly, adding the name of your component: components: products: - filename: your-component-name.owl 3) Add the component to your catalog file (src/ontology/catalog-v001.xml) 4) Add the component to the edit file (src/ontology/cl-edit.obo) for .obo formats: import: http://purl.obolibrary.org/obo/cl/components/your-component-name.owl for .owl formats: Import() 5) Refresh your repo by running sh run.sh make update_repo - this should create a new file in src/ontology/components. 6) In your custom makefile (src/ontology/cl.Makefile) add a goal for your custom make file. In this example, the goal is a ROBOT template. $(COMPONENTSDIR)/your-component-name.owl: $(SRC) ../templates/your-component-template.tsv $(ROBOT) template --template ../templates/your-component-template.tsv \\ annotate --ontology-iri $(ONTBASE)/$@ --output $(COMPONENTSDIR)/your-component-name.owl (If using a ROBOT template, do not forget to add your template tsv in src/templates/) 7) Make the file by running sh run.sh make components/your-component-name.owl","title":"Adding components to an ODK repo"},{"location":"patterns/cellBearerOfQuality/","text":"cellBearerOfQuality \u00b6 http://purl.obolibrary.org/obo/cl/cellBearerOfQuality Description \u00b6 A cell that has a specific quality, such as binucleate. Contributors \u00b6 https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165 Name \u00b6 { quality } { cell } Annotations \u00b6 exact_synonym : { quality }d { cell } Definition \u00b6 Any { cell } that is { quality } Equivalent to \u00b6 { cell } and ( bearer of some { quality }) Data preview \u00b6 defined_class defined_class_label cell cell_label quality quality_label CL:0001061 abnormal cell CL:0000000 cell PATO:0000460 abnormal CL:0000225 anucleate cell CL:0000003 native cell PATO:0001405 anucleate CL:0000227 binucleate cell CL:0000003 native cell PATO:0001406 binucleate CL:0000103 bipolar neuron CL:0000099 interneuron PATO:0070006 bipolar morphology CL:4023077 bitufted neuron CL:0000099 interneuron PATO:0070012 bitufted cell morphology See full table here","title":"Cell, bearer of quality"},{"location":"patterns/cellBearerOfQuality/#cellbearerofquality","text":"http://purl.obolibrary.org/obo/cl/cellBearerOfQuality","title":"cellBearerOfQuality"},{"location":"patterns/cellBearerOfQuality/#description","text":"A cell that has a specific quality, such as binucleate.","title":"Description"},{"location":"patterns/cellBearerOfQuality/#contributors","text":"https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165","title":"Contributors"},{"location":"patterns/cellBearerOfQuality/#name","text":"{ quality } { cell }","title":"Name"},{"location":"patterns/cellBearerOfQuality/#annotations","text":"exact_synonym : { quality }d { cell }","title":"Annotations"},{"location":"patterns/cellBearerOfQuality/#definition","text":"Any { cell } that is { quality }","title":"Definition"},{"location":"patterns/cellBearerOfQuality/#equivalent-to","text":"{ cell } and ( bearer of some { quality })","title":"Equivalent to"},{"location":"patterns/cellBearerOfQuality/#data-preview","text":"defined_class defined_class_label cell cell_label quality quality_label CL:0001061 abnormal cell CL:0000000 cell PATO:0000460 abnormal CL:0000225 anucleate cell CL:0000003 native cell PATO:0001405 anucleate CL:0000227 binucleate cell CL:0000003 native cell PATO:0001406 binucleate CL:0000103 bipolar neuron CL:0000099 interneuron PATO:0070006 bipolar morphology CL:4023077 bitufted neuron CL:0000099 interneuron PATO:0070012 bitufted cell morphology See full table here","title":"Data preview"},{"location":"patterns/cellCapableOfBiologicalProcess/","text":"cellCapableOfBiologicalProcess \u00b6 http://purl.obolibrary.org/obo/cl/cellCapableOfBiologicalProcess Description \u00b6 Any cell that is involved in/capable of a particular biological process, such as acid secretion. Name \u00b6 { biological_process } { cell } Definition \u00b6 A { cell } that is capable of { biological_process }. Equivalent to \u00b6 { cell } and ( capable of some { biological_process }) Data preview \u00b6 defined_class defined_class_label biological_process biological_process_label cell cell_label CL:0000236 B cell GO:0019724 B cell mediated immunity CL:0000945 lymphocyte of B lineage CL:0000492 CD4-positive helper T cell GO:0001816 cytokine production CL:0000624 CD4-positive, alpha-beta T cell CL:0000795 CD8-positive, alpha-beta regulatory T cell GO:0050777 negative regulation of immune response CL:0000625 CD8-positive, alpha-beta T cell CL:0011005 GABAergic interneuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000099 interneuron CL:0000617 GABAergic neuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000540 neuron See full table here","title":"Cell, capable of Biological Process"},{"location":"patterns/cellCapableOfBiologicalProcess/#cellcapableofbiologicalprocess","text":"http://purl.obolibrary.org/obo/cl/cellCapableOfBiologicalProcess","title":"cellCapableOfBiologicalProcess"},{"location":"patterns/cellCapableOfBiologicalProcess/#description","text":"Any cell that is involved in/capable of a particular biological process, such as acid secretion.","title":"Description"},{"location":"patterns/cellCapableOfBiologicalProcess/#name","text":"{ biological_process } { cell }","title":"Name"},{"location":"patterns/cellCapableOfBiologicalProcess/#definition","text":"A { cell } that is capable of { biological_process }.","title":"Definition"},{"location":"patterns/cellCapableOfBiologicalProcess/#equivalent-to","text":"{ cell } and ( capable of some { biological_process })","title":"Equivalent to"},{"location":"patterns/cellCapableOfBiologicalProcess/#data-preview","text":"defined_class defined_class_label biological_process biological_process_label cell cell_label CL:0000236 B cell GO:0019724 B cell mediated immunity CL:0000945 lymphocyte of B lineage CL:0000492 CD4-positive helper T cell GO:0001816 cytokine production CL:0000624 CD4-positive, alpha-beta T cell CL:0000795 CD8-positive, alpha-beta regulatory T cell GO:0050777 negative regulation of immune response CL:0000625 CD8-positive, alpha-beta T cell CL:0011005 GABAergic interneuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000099 interneuron CL:0000617 GABAergic neuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000540 neuron See full table here","title":"Data preview"},{"location":"patterns/cellHasPlasmaMembranePartX/","text":"cellHasPlasmaMembranePartX \u00b6 http://purl.obolibrary.org/obo/cl/cellHasPlasmaMembranePartX Description \u00b6 A cell type that is characterized by a plasma membrane part, such as a cilium or receptor. Note - that this is only good for cells defined by a single plasma membrane receptor. Contributors \u00b6 https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165 Name \u00b6 { cell } { plasma_membrane } Annotations \u00b6 exact_synonym : { cell }-positive { plasma_membrane } Definition \u00b6 A { cell } that has a { plasma_membrane }. Equivalent to \u00b6 { cell } and ( has plasma membrane part some { plasma_membrane })","title":"Cell, has plasme membrane"},{"location":"patterns/cellHasPlasmaMembranePartX/#cellhasplasmamembranepartx","text":"http://purl.obolibrary.org/obo/cl/cellHasPlasmaMembranePartX","title":"cellHasPlasmaMembranePartX"},{"location":"patterns/cellHasPlasmaMembranePartX/#description","text":"A cell type that is characterized by a plasma membrane part, such as a cilium or receptor. Note - that this is only good for cells defined by a single plasma membrane receptor.","title":"Description"},{"location":"patterns/cellHasPlasmaMembranePartX/#contributors","text":"https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165","title":"Contributors"},{"location":"patterns/cellHasPlasmaMembranePartX/#name","text":"{ cell } { plasma_membrane }","title":"Name"},{"location":"patterns/cellHasPlasmaMembranePartX/#annotations","text":"exact_synonym : { cell }-positive { plasma_membrane }","title":"Annotations"},{"location":"patterns/cellHasPlasmaMembranePartX/#definition","text":"A { cell } that has a { plasma_membrane }.","title":"Definition"},{"location":"patterns/cellHasPlasmaMembranePartX/#equivalent-to","text":"{ cell } and ( has plasma membrane part some { plasma_membrane })","title":"Equivalent to"},{"location":"patterns/cellPartOfAnatomicalEntity/","text":"cellPartOfAnatomicalEntity \u00b6 http://purl.obolibrary.org/obo/cl/cellPartOfAnatomicalEntity Description \u00b6 A cell that is part of an anatomical entity. Contributors \u00b6 https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165 Name \u00b6 { cell } { anatomical_entity } Definition \u00b6 Any { cell } that is part of a { anatomical_entity }. Equivalent to \u00b6 { cell } and ( part of some { anatomical_entity }) Data preview \u00b6 defined_class defined_class_label anatomical_entity anatomical_entity_label cell cell_label CL:0009032 B cell of appendix UBERON:0001154 vermiform appendix CL:0000236 B cell CL:0009045 B cell of medullary sinus of lymph node UBERON:0009744 lymph node medullary sinus CL:0000236 B cell CL:0010007 His-Purkinje system cell UBERON:0004146 His-Purkinje system CL:0000003 native cell CL:0002680 PP cell of intestine UBERON:0000160 intestine CL:0000696 PP cell CL:0009015 Peyer's patch follicular dendritic cell UBERON:0001211 Peyer's patch CL:0000442 follicular dendritic cell See full table here","title":"Cell, part of anatomical entity"},{"location":"patterns/cellPartOfAnatomicalEntity/#cellpartofanatomicalentity","text":"http://purl.obolibrary.org/obo/cl/cellPartOfAnatomicalEntity","title":"cellPartOfAnatomicalEntity"},{"location":"patterns/cellPartOfAnatomicalEntity/#description","text":"A cell that is part of an anatomical entity.","title":"Description"},{"location":"patterns/cellPartOfAnatomicalEntity/#contributors","text":"https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165","title":"Contributors"},{"location":"patterns/cellPartOfAnatomicalEntity/#name","text":"{ cell } { anatomical_entity }","title":"Name"},{"location":"patterns/cellPartOfAnatomicalEntity/#definition","text":"Any { cell } that is part of a { anatomical_entity }.","title":"Definition"},{"location":"patterns/cellPartOfAnatomicalEntity/#equivalent-to","text":"{ cell } and ( part of some { anatomical_entity })","title":"Equivalent to"},{"location":"patterns/cellPartOfAnatomicalEntity/#data-preview","text":"defined_class defined_class_label anatomical_entity anatomical_entity_label cell cell_label CL:0009032 B cell of appendix UBERON:0001154 vermiform appendix CL:0000236 B cell CL:0009045 B cell of medullary sinus of lymph node UBERON:0009744 lymph node medullary sinus CL:0000236 B cell CL:0010007 His-Purkinje system cell UBERON:0004146 His-Purkinje system CL:0000003 native cell CL:0002680 PP cell of intestine UBERON:0000160 intestine CL:0000696 PP cell CL:0009015 Peyer's patch follicular dendritic cell UBERON:0001211 Peyer's patch CL:0000442 follicular dendritic cell See full table here","title":"Data preview"},{"location":"patterns/overview/","text":"Pattern directory \u00b6 This is a listing of all the patterns hosted as part of this directory Patterns in \u00b6 Cell bearer of quality \u00b6 A cell that has a specific quality, such as binucleate. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellBearerOfQuality Name cellBearerOfQuality Classes CL:0000000, PATO:0000001, Variables cell (CL:0000000), quality (PATO:0000001), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples mondo Data preview: \u00b6 defined:class defined:class:label cell cell:label quality quality:label CL:0001061 abnormal cell CL:0000000 cell PATO:0000460 abnormal CL:0000225 anucleate cell CL:0000003 native cell PATO:0001405 anucleate CL:0000227 binucleate cell CL:0000003 native cell PATO:0001406 binucleate CL:0000103 bipolar neuron CL:0000099 interneuron PATO:0070006 bipolar morphology CL:4023077 bitufted neuron CL:0000099 interneuron PATO:0070012 bitufted cell morphology See full table here Cell capable of biological process \u00b6 Any cell that is involved in/capable of a particular biological process, such as acid secretion. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellCapableOfBiologicalProcess Name cellCapableOfBiologicalProcess Classes CL:0000000, GO:0008150, Variables cell (CL:0000000), biological_process (GO:0008150), Contributors Examples mondo Data preview: \u00b6 defined:class defined:class:label biological:process biological:process:label cell cell:label CL:0000236 B cell GO:0019724 B cell mediated immunity CL:0000945 lymphocyte of B lineage CL:0000492 CD4-positive helper T cell GO:0001816 cytokine production CL:0000624 CD4-positive, alpha-beta T cell CL:0000795 CD8-positive, alpha-beta regulatory T cell GO:0050777 negative regulation of immune response CL:0000625 CD8-positive, alpha-beta T cell CL:0011005 GABAergic interneuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000099 interneuron CL:0000617 GABAergic neuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000540 neuron See full table here Cell has plasma membrane part x \u00b6 A cell type that is characterized by a plasma membrane part, such as a cilium or receptor. Note - that this is only good for cells defined by a single plasma membrane receptor. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellHasPlasmaMembranePartX Name cellHasPlasmaMembranePartX Classes CL:0000000, CL:0000003, GO:0005886, Variables cell (CL:0000003), plasma_membrane (GO:0005886), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples Cell part of anatomical entity \u00b6 A cell that is part of an anatomical entity. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellPartOfAnatomicalEntity Name cellPartOfAnatomicalEntity Classes CL:0000000, UBERON:0001062, Variables cell (CL:0000000), anatomical_entity (UBERON:0001062), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples mondo Data preview: \u00b6 defined:class defined:class:label anatomical:entity anatomical:entity:label cell cell:label CL:0009032 B cell of appendix UBERON:0001154 vermiform appendix CL:0000236 B cell CL:0009045 B cell of medullary sinus of lymph node UBERON:0009744 lymph node medullary sinus CL:0000236 B cell CL:0010007 His-Purkinje system cell UBERON:0004146 His-Purkinje system CL:0000003 native cell CL:0002680 PP cell of intestine UBERON:0000160 intestine CL:0000696 PP cell CL:0009015 Peyer's patch follicular dendritic cell UBERON:0001211 Peyer's patch CL:0000442 follicular dendritic cell See full table here Taxon specific \u00b6 A cell that is restricted to a specific taxon, such as CL:0001200 'lymphocyte of B lineage, CD19-positive' are only in mammals. Note - this is not to be used for any cell that is restricted to a taxon, this is for taxon-specific subclasses of existing cell types. This should hardly ever be used. Attribute Info IRI http://purl.obolibrary.org/obo/cl/taxonSpecific Name taxonSpecific Classes CL:0000000, NCBITaxon:1, Variables cell (CL:0000000), taxon (NCBITaxon:1), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples","title":"Design Patterns Overview"},{"location":"patterns/overview/#pattern-directory","text":"This is a listing of all the patterns hosted as part of this directory","title":"Pattern directory"},{"location":"patterns/overview/#patterns-in","text":"","title":"Patterns in"},{"location":"patterns/overview/#cell-bearer-of-quality","text":"A cell that has a specific quality, such as binucleate. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellBearerOfQuality Name cellBearerOfQuality Classes CL:0000000, PATO:0000001, Variables cell (CL:0000000), quality (PATO:0000001), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples mondo","title":"Cell bearer of quality"},{"location":"patterns/overview/#data-preview","text":"defined:class defined:class:label cell cell:label quality quality:label CL:0001061 abnormal cell CL:0000000 cell PATO:0000460 abnormal CL:0000225 anucleate cell CL:0000003 native cell PATO:0001405 anucleate CL:0000227 binucleate cell CL:0000003 native cell PATO:0001406 binucleate CL:0000103 bipolar neuron CL:0000099 interneuron PATO:0070006 bipolar morphology CL:4023077 bitufted neuron CL:0000099 interneuron PATO:0070012 bitufted cell morphology See full table here","title":"Data preview:"},{"location":"patterns/overview/#cell-capable-of-biological-process","text":"Any cell that is involved in/capable of a particular biological process, such as acid secretion. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellCapableOfBiologicalProcess Name cellCapableOfBiologicalProcess Classes CL:0000000, GO:0008150, Variables cell (CL:0000000), biological_process (GO:0008150), Contributors Examples mondo","title":"Cell capable of biological process"},{"location":"patterns/overview/#data-preview_1","text":"defined:class defined:class:label biological:process biological:process:label cell cell:label CL:0000236 B cell GO:0019724 B cell mediated immunity CL:0000945 lymphocyte of B lineage CL:0000492 CD4-positive helper T cell GO:0001816 cytokine production CL:0000624 CD4-positive, alpha-beta T cell CL:0000795 CD8-positive, alpha-beta regulatory T cell GO:0050777 negative regulation of immune response CL:0000625 CD8-positive, alpha-beta T cell CL:0011005 GABAergic interneuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000099 interneuron CL:0000617 GABAergic neuron GO:0061534 gamma-aminobutyric acid secretion, neurotransmission CL:0000540 neuron See full table here","title":"Data preview:"},{"location":"patterns/overview/#cell-has-plasma-membrane-part-x","text":"A cell type that is characterized by a plasma membrane part, such as a cilium or receptor. Note - that this is only good for cells defined by a single plasma membrane receptor. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellHasPlasmaMembranePartX Name cellHasPlasmaMembranePartX Classes CL:0000000, CL:0000003, GO:0005886, Variables cell (CL:0000003), plasma_membrane (GO:0005886), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples","title":"Cell has plasma membrane part x"},{"location":"patterns/overview/#cell-part-of-anatomical-entity","text":"A cell that is part of an anatomical entity. Attribute Info IRI http://purl.obolibrary.org/obo/cl/cellPartOfAnatomicalEntity Name cellPartOfAnatomicalEntity Classes CL:0000000, UBERON:0001062, Variables cell (CL:0000000), anatomical_entity (UBERON:0001062), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples mondo","title":"Cell part of anatomical entity"},{"location":"patterns/overview/#data-preview_2","text":"defined:class defined:class:label anatomical:entity anatomical:entity:label cell cell:label CL:0009032 B cell of appendix UBERON:0001154 vermiform appendix CL:0000236 B cell CL:0009045 B cell of medullary sinus of lymph node UBERON:0009744 lymph node medullary sinus CL:0000236 B cell CL:0010007 His-Purkinje system cell UBERON:0004146 His-Purkinje system CL:0000003 native cell CL:0002680 PP cell of intestine UBERON:0000160 intestine CL:0000696 PP cell CL:0009015 Peyer's patch follicular dendritic cell UBERON:0001211 Peyer's patch CL:0000442 follicular dendritic cell See full table here","title":"Data preview:"},{"location":"patterns/overview/#taxon-specific","text":"A cell that is restricted to a specific taxon, such as CL:0001200 'lymphocyte of B lineage, CD19-positive' are only in mammals. Note - this is not to be used for any cell that is restricted to a taxon, this is for taxon-specific subclasses of existing cell types. This should hardly ever be used. Attribute Info IRI http://purl.obolibrary.org/obo/cl/taxonSpecific Name taxonSpecific Classes CL:0000000, NCBITaxon:1, Variables cell (CL:0000000), taxon (NCBITaxon:1), Contributors 0000-0001-5208-3432 , 0000-0002-6601-2165 , Examples","title":"Taxon specific"},{"location":"patterns/taxonSpecific/","text":"taxonSpecific \u00b6 http://purl.obolibrary.org/obo/cl/taxonSpecific Description \u00b6 A cell that is restricted to a specific taxon, such as CL:0001200 'lymphocyte of B lineage, CD19-positive' are only in mammals. Note - this is not to be used for any cell that is restricted to a taxon, this is for taxon-specific subclasses of existing cell types. This should hardly ever be used. Contributors \u00b6 https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165 Name \u00b6 { cell }, { taxon } Definition \u00b6 Any { cell } that is part of a { taxon }. Equivalent to \u00b6 { cell } and ( in taxon some { taxon })","title":"Cell, taxon specific"},{"location":"patterns/taxonSpecific/#taxonspecific","text":"http://purl.obolibrary.org/obo/cl/taxonSpecific","title":"taxonSpecific"},{"location":"patterns/taxonSpecific/#description","text":"A cell that is restricted to a specific taxon, such as CL:0001200 'lymphocyte of B lineage, CD19-positive' are only in mammals. Note - this is not to be used for any cell that is restricted to a taxon, this is for taxon-specific subclasses of existing cell types. This should hardly ever be used.","title":"Description"},{"location":"patterns/taxonSpecific/#contributors","text":"https://orcid.org/0000-0001-5208-3432 https://orcid.org/0000-0002-6601-2165","title":"Contributors"},{"location":"patterns/taxonSpecific/#name","text":"{ cell }, { taxon }","title":"Name"},{"location":"patterns/taxonSpecific/#definition","text":"Any { cell } that is part of a { taxon }.","title":"Definition"},{"location":"patterns/taxonSpecific/#equivalent-to","text":"{ cell } and ( in taxon some { taxon })","title":"Equivalent to"}]} \ No newline at end of file diff --git a/search/worker.js b/search/worker.js new file mode 100644 index 000000000..8628dbce9 --- /dev/null +++ b/search/worker.js @@ -0,0 +1,133 @@ +var base_path = 'function' === typeof importScripts ? '.' : '/search/'; +var allowSearch = false; +var index; +var documents = {}; +var lang = ['en']; +var data; + +function getScript(script, callback) { + console.log('Loading script: ' + script); + $.getScript(base_path + script).done(function () { + callback(); + }).fail(function (jqxhr, settings, exception) { + console.log('Error: ' + exception); + }); +} + +function getScriptsInOrder(scripts, callback) { + if (scripts.length === 0) { + callback(); + return; + } + getScript(scripts[0], function() { + getScriptsInOrder(scripts.slice(1), callback); + }); +} + +function loadScripts(urls, callback) { + if( 'function' === typeof importScripts ) { + importScripts.apply(null, urls); + callback(); + } else { + getScriptsInOrder(urls, callback); + } +} + +function onJSONLoaded () { + data = JSON.parse(this.responseText); + var scriptsToLoad = ['lunr.js']; + if (data.config && data.config.lang && data.config.lang.length) { + lang = data.config.lang; + } + if (lang.length > 1 || lang[0] !== "en") { + scriptsToLoad.push('lunr.stemmer.support.js'); + if (lang.length > 1) { + scriptsToLoad.push('lunr.multi.js'); + } + if (lang.includes("ja") || lang.includes("jp")) { + scriptsToLoad.push('tinyseg.js'); + } + for (var i=0; i < lang.length; i++) { + if (lang[i] != 'en') { + scriptsToLoad.push(['lunr', lang[i], 'js'].join('.')); + } + } + } + loadScripts(scriptsToLoad, onScriptsLoaded); +} + +function onScriptsLoaded () { + console.log('All search scripts loaded, building Lunr index...'); + if (data.config && data.config.separator && data.config.separator.length) { + lunr.tokenizer.separator = new RegExp(data.config.separator); + } + + if (data.index) { + index = lunr.Index.load(data.index); + data.docs.forEach(function (doc) { + documents[doc.location] = doc; + }); + console.log('Lunr pre-built index loaded, search ready'); + } else { + index = lunr(function () { + if (lang.length === 1 && lang[0] !== "en" && lunr[lang[0]]) { + this.use(lunr[lang[0]]); + } else if (lang.length > 1) { + this.use(lunr.multiLanguage.apply(null, lang)); // spread operator not supported in all browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility + } + this.field('title'); + this.field('text'); + this.ref('location'); + + for (var i=0; i < data.docs.length; i++) { + var doc = data.docs[i]; + this.add(doc); + documents[doc.location] = doc; + } + }); + console.log('Lunr index built, search ready'); + } + allowSearch = true; + postMessage({config: data.config}); + postMessage({allowSearch: allowSearch}); +} + +function init () { + var oReq = new XMLHttpRequest(); + oReq.addEventListener("load", onJSONLoaded); + var index_path = base_path + '/search_index.json'; + if( 'function' === typeof importScripts ){ + index_path = 'search_index.json'; + } + oReq.open("GET", index_path); + oReq.send(); +} + +function search (query) { + if (!allowSearch) { + console.error('Assets for search still loading'); + return; + } + + var resultDocuments = []; + var results = index.search(query); + for (var i=0; i < results.length; i++){ + var result = results[i]; + doc = documents[result.ref]; + doc.summary = doc.text.substring(0, 200); + resultDocuments.push(doc); + } + return resultDocuments; +} + +if( 'function' === typeof importScripts ) { + onmessage = function (e) { + if (e.data.init) { + init(); + } else if (e.data.query) { + postMessage({ results: search(e.data.query) }); + } else { + console.error("Worker - Unrecognized message: " + e); + } + }; +} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..0f8724efd --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 000000000..241106e89 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/taxon-restrictions/index.html b/taxon-restrictions/index.html new file mode 100644 index 000000000..010e2276f --- /dev/null +++ b/taxon-restrictions/index.html @@ -0,0 +1,1023 @@ + + + + + + + + + + + + + + + + + + + + + + + Taxon constraints - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Taxon constraints

+ +

The cell ontology (CL) follows certain conventions regarding taxon constraints. +Please review the following pages before adding taxon constraints to CL terms.

+

Explanation of taxon constraints can be found here: +https://oboacademy.github.io/obook/explanation/taxon-constraints-explainer/

+

A how-to guide can be found here: +https://oboacademy.github.io/obook/howto/add-taxon-restrictions/

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/textual_definitions_SOP/index.html b/textual_definitions_SOP/index.html new file mode 100644 index 000000000..83f15b8f2 --- /dev/null +++ b/textual_definitions_SOP/index.html @@ -0,0 +1,1151 @@ + + + + + + + + + + + + + + + + + + + textual definitions SOP - CL Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + +

textual definitions SOP

+ +

Guide to writing textual definitions on CL

+

Relevant background material

+ +

Background

+

It is standard ontology engineering practise to aim for minimal, concise ontology term definitions. However many cell types can be reliably identified by more than one set of properties: functional, structural, gene expression. This makes it hard to choose which properties to include if we are aiming for a minimal definition. Users of the cell ontology also come from different disciplines/perspectives and have different types of information and levels of detail available when they annotate a term or browse a resource. We need to be able to support users from multiple disciplines with a definition that allows them to visualise and identify the cell type being defined. In some cases (e.g. transcriptomically defined types or 't-types') identification of the cell type requires links to reference data.

+

Here are a couple of examples of minimal definitions that are correct but not useful to most users:

+
    +
  1. We could minimally define a corneal endothelial cell as 'Any endothelial cell that is part of the cornea'. This may well be sufficient for an expert in the anatomy and biology of the cornea, but to most biologists, the term "endothelial cell" brings to mind the principal cell types of lymphatic or blood vessels. However, "corneal endothelium" refers to a monolayer of flat cells on the underside of the cornea.
  2. +
  3. Similarly, a perfectly accurate minimal definition of a type II pneuomocyte is an epithelial cell that has an 'alveolar lamellar body' (a unique structure only found in these cell types). But this is useless information to a user who knows nothing about this structure (many biologists) or who is annotating data that does not resolve this structure.
  4. +
+

A second use of ontologies is to encode knowledge in the form of useful formal links between ontology terms. For example, in CL we record function and cell components via links to gene ontology terms, location via links to CL and lineage via links to other CL terms. Not all of this information is particularly useful for recognising a cell type, but it is of use to our users and so we often record it in CL using formal relationships. This is relevant to ontology definitions because it is good practise for formal and textual definitions to match, and textual definitions are the place we encode supporting references.

+

We can't of course, include every known piece of information about a cell in a definition (e.g. all genes expressed). However extended information is useful to our users - especially where it includes potential marker sets and information relevant to human physiology and disease. To support this, we have an additioal extended description field which can contain information about additional marker sets, minor (secondary) functions and disease. It can also contain information about properties that may not apply to all subclasses - this is especially useful for t-types in the brain where there is typically sparse knowledge of the extent of variation in morphology and function under each t-type. In these cases, information should be included about where these proporites do apply.

+

SOP

+

Definition

+

Text in the definition field should be no longer than one short paragraph and should follow a classic genus, differentia and gloss type structure: + - genus: what type of cell is it (e.g. epithelial cell) + - differentia: a list of properties that can be used to distinguish it from other similar cell types, especially those in the same tissue/organ context and those of the same genus. This SHOULD include location unless the term is so abstract that this is not possible. We should be liberal in listing properties here in order to support multiple communities who will have different types of data and understanding. Structural and functional properties are preferred over molecular markers unless cell types are named for these markers or they are generally accepted as definitional by the community. Care should be taken not to attach species-specific markers to species-general cell types. Please note that while CL supports recording multiple marker sets for cell types, ideally with provenance, evidence and confidence, this is supported is outside of the core definition text. + - gloss: Additional information not required for identification, including but not limited to, all assertions recorded in formal local assertions not covered in the differentia. This can include information about developmental origins, processes the cell is capable of (for example these may be secondary processes like a tendon cell's role stimulating an immune response when damaged) or roles that the cell may have.

+

Extended description text.

+

This is optional descriptive information in the rdfs:comment value (although we may switch to a dedicated annotation property in future). The text should be referenced following standard academic practice (minirefs in text, e.g. Avola et al., 2024). It can include: + - Descriptions of marker genes and marker gene sets. These MUST include species and provenance, and ideally evidence (e.g. identified by use of the NS-Forest algorithm on dataset x; identified by in-situ hybridization) and confidence. + - Information specific to only some species or subtypes (where the applicability is known this should be made clear). + - Information about the role of the cell type more broadly in disease and physiology.

+

The comment section may also be used to record evidence and and name/synonym disambiguation.

+

Defining transcriptomic types (t-types):

+

Some cell types are defined with reference to transcriptomic data. This is especially common in brain datasets. In these cases, naming is often based on semi-automated transfer of names that are based on some specific set of properties. We do not always know how widely those properties apply so need to be careful in choosing them for differentia. Extended multi-modal descriptions may be available, for example based on patch-seq data, but this is typically derived from very sparse data, so such information belongs in the extended definition, along with details of the brain regions where these properties have been assayed.

+

Definitions for these follow a different pattern: + - First sentence: "A transcriptomically distinct { genus } with { description of primary differentia here }. Second sentence may include more differentia.
+ - Gloss: see above + - Last sentence: The standard transcriptomic reference data for this cell type can be found on the { site } under { human readable details of how to access dataset }, { human readable details of annotation key/value pair that marks the reference cell set.

+

Example:

+

label: +A transcriptomically distinct intratelencephalic-projecting glutamatergic neuron with a soma found between cortical layer 2-4. The standard transcriptomic reference data for this cell type can be found on the CellxGene census under the collection: "Transcriptomic cytoarchitecture reveals principles of human neocortex organization", dataset: "Supercluster: IT-projecting excitatory neurons", Author Categories: "CrossArea_subclass", value: L2/3 IT.

+

Comment: In the barrel cortex (of rodents), these neurons have thin-tufted apical dendrites, extend their axonal projections into L5 in the neocortex and have a hyperpolarised resting membrane potential (Harris & Shepherd 2016). Historically, these neurons were identified in cortical layer 2/3. MERFISH data shows that this intratelencephalic-projecting glutametergic neuron can have its soma in layer 2/3, 4B, 4C (Jorstad et al., 2023). The position of the soma in layer 4b and 4C is less frequent for this neuronal type in comparison to cortical layer 2/3.

+

Note: the reference dataset should MUST also be referenced directly via an xref.

+

_Axiomatisation of t-types:

+

Axiomatisation of t-types is tricky for a number of reasons: +1. Transcriptomic hierarchy does not necessarily follow property based hierarchy, for example we might define SST cortical interneurons as any cortical interneuron expressing SST. However, in transcriptomic hierarchies, one SST expressing type (SST CHODL) is only distantly related to the other SST cells and so is treated as a disjoint type (e.g. see Jorstad et al., 2023). +2. We have limited knowledge about how widely particular properties of t-types apply.

+

In the former case, it is sufficient to express the property with a subClassOf axiom (avoiding use of equivalent class axioms). In the latter, mention of the property should be confined to the extended description, or in some cases we may use annotation properties. For example, in linking a single transcriptomic cell type to multiple brain regions or layers, an annotation property must be used.

+

Given the limited knowledge we have about how and whether they are unique to a particular cell type, care needs to be taken in adding formal axioms recording them. Where there is a possibility that it is important to limit clauses in EquivalentClass expressions.

+

We have a standard pattern that can be used to convert transcriptomic heirarchies into SubClassOf hierarchies - using equivalence axioms with a 'has_examplar' clause with value cell set (see Tan et al., 2023 for details). However care should be taken in using this given the potential for inheritance of properties that don't apply to all subclusters in a transcriptomic hierarchy. A formal link to a defining cell set can be represented using subClassOf in order to avoid this.

+

References

+
    +
  • Harris, Kenneth D., and Gordon M. G. Shepherd. 2015. “The Neocortical Circuit: Themes and Variations.” Nature Neuroscience 18 (2): 170–81. https://doi.org/10.1038/nn.3917.
  • +
  • Jorstad, Nikolas L., Jennie Close, Nelson Johansen, Anna Marie Yanny, Eliza R. Barkan, Kyle J. Travaglini, Darren Bertagnolli, et al. 2023. “Transcriptomic Cytoarchitecture Reveals Principles of Human Neocortex Organization.” Science 382 (6667): eadf6812. https://doi.org/10.1126/science.adf6812.
  • +
  • Tan, Shawn Zheng Kai, Huseyin Kir, Brian D. Aevermann, Tom Gillespie, Nomi Harris, Michael J. Hawrylycz, Nikolas L. Jorstad, et al. 2023. “Brain Data Standards - A Method for Building Data-Driven Cell-Type Ontologies.” Scientific Data 10 (1): 50. https://doi.org/10.1038/s41597-022-01886-2.
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file