- * Yields the tag identifiers in the `TagSystem` associated with this
- * constructor.
- *
- * ※ The first two arguments of this function are bound when
- * generating the value of `TagSystem::Tag`. It isn’t possible to
- * access this function in its unbound form from outside this module.
- */
- static *identifiers(system, storage) {
- for (const [identifier, instance] of storage.entries()) {
- // Iterate over the entries and yield the ones which are `Tag`s
- // in this `TagSystem`.
- if (Tag.getSystem(instance) == system) {
- // The current instance is a `Tag` in this `TagSystem`.
- yield identifier;
- } else {
- // The current instance is not a `Tag` in this `TagSystem`.
- /* do nothing */
- }
- }
- }
-
- /**
- * Returns a new `Tag` constructed from the provided data and with
- * the provided identifier.
- *
- * ※ This function will not work if called directly from `Tag` (and
- * nor is it available *to* be called as such from outside this
- * module). It must be called from a `TagSystem::Tag` bound
- * constructor.
- *
- * ※ This function is not really intended for public usage.
- */
- static [Storage.toInstance](_system, _storage, data, identifier) {
- const tag = new this(data.kind);
- tag.#identifier = `${identifier}`;
- tag.#persistedData = tagData(data);
- tag.#data = tagData(data);
- return tag;
- }
-
- static {
- // Overwrite the default `::constructor` method to instead give the
- // actual (bound) constructor which was used to generate a given
- // `Tag`.
- Object.defineProperties(this.prototype, {
- constructor: {
- configurable: true,
- enumerable: false,
- get() {
- // All `Tag`s are constructed via the `.Tag` constructor
- // available in their `TagSystem`; return it.
- return this.#system.Tag;
- },
- set: undefined,
- },
- });
- }
-
- /**
- * Adds the provided label(s) to this `Tag` as alternate labels, then
- * returns this `Tag`.
- */
- addAltLabel(...labels) {
- const altLabels = this.#data.altLabel;
- let objectLabels = null; // initialized on first use
- for (const $ of labels) {
- // Iterate over each provided label and attempt to add it.
- const literal = langString($);
- if (Object(literal) === literal) {
- // The current label is a language‐tagged string.
- objectLabels ??= [...function* () {
- for (const altLabel of altLabels) {
- // Iterate over the existing labels and yield the
- // language‐tagged strings.
- if (Object(altLabel) === altLabel) {
- // The current existing label is a language‐tagged
- // string.
- yield altLabel;
- } else {
- // The current existing label is not a language‐tagged
- // string.
- /* do nothing */
- }
- }
- }()];
- if (
- objectLabels.some((objectLabel) =>
- objectLabel["@value"] == literal["@value"] &&
- objectLabel["@language"] == literal["@language"]
- )
- ) {
- // There is a match with the current label in the existing
- // labels.
- /* do nothing */
- } else {
- // There is no match and this label must be added.
- altLabels.add(literal);
- objectLabels.push(literal);
- }
- } else {
- // The current label is a simple string.
- altLabels.add(literal);
- }
- }
- return this;
- }
-
- /**
- * Adds the provided tags to the list of tags that this `Tag` is
- * narrower than, then returns this `Tag`.
- *
- * Arguments may be string identifiers or objects with an
- * `.identifier` property.
- */
- addBroaderTag(...tags) {
- const storage = this.#storage;
- const broader = this.#data.broader;
- for (const $ of tags) {
- // Iterate over each tag and attempt to set it as broader than
- // this `Tag`.
- const identifier = toIdentifier($);
- if (identifier == null) {
- // ☡ The current tag has no identifier.
- throw new TypeError(
- "Cannot assign broader to Tag: Identifier must not be nullish.",
- );
- } else if (broader.has(identifier)) {
- // Short‐circuit: The identifier is already something this
- // `Tag` is narrower than.
- /* do nothing */
- } else {
- // The current tag has an identifier.
- const tag = storage.get(identifier);
- if (tag == null) {
- // ☡ The current tag has not been persisted to this `Tag`’s
- // storage.
- throw new RangeError(
- `Cannot assign broader to Tag: Identifier is not persisted: ${identifier}.`,
- );
- } else if (!this.#isTagInStorage(tag)) {
- // ☡ The current tag is not a tag in the correct tag system.
- throw new TypeError(
- `Cannot assign broader to Tag: Tags must be from the same Tag System, but got: ${identifier}.`,
- );
- } else {
- // The current tag is a tag in the correct tag system; add
- // its identifier.
- broader.add(identifier);
- }
- }
- }
- return this;
- }
-
- /**
- * Adds the provided label(s) to this `Tag` as hidden labels, then
- * returns this `Tag`.
- */
- addHiddenLabel(...labels) {
- const hiddenLabels = this.#data.hiddenLabel;
- let objectLabels = null; // initialized on first use
- for (const $ of labels) {
- // Iterate over each provided label and attempt to add it.
- const literal = langString($);
- if (Object(literal) === literal) {
- // The current label is a language‐tagged string.
- objectLabels ??= [...function* () {
- for (const hiddenLabel of hiddenLabels) {
- // Iterate over the existing labels and yield the
- // language‐tagged strings.
- if (Object(hiddenLabel) === hiddenLabel) {
- // The current existing label is a language‐tagged
- // string.
- yield hiddenLabel;
- } else {
- // The current existing label is not a language‐tagged
- // string.
- /* do nothing */
- }
- }
- }()];
- if (
- objectLabels.some((objectLabel) =>
- objectLabel["@value"] == literal["@value"] &&
- objectLabel["@language"] == literal["@language"]
- )
- ) {
- // There is a match with the current label in the existing
- // labels.
- /* do nothing */
- } else {
- // There is no match and this label must be added.
- hiddenLabels.add(literal);
- objectLabels.push(literal);
- }
- } else {
- // The current label is a simple string.
- hiddenLabels.add(literal);
- }
- }
- return this;
- }
-
- /**
- * Adds the provided tags to the list of tags that this `Tag` is in
- * canon with, then returns this `Tag`.
- *
- * Arguments may be string identifiers or objects with an
- * `.identifier` property.
- *
- * ☡ This method will throw if a provided argument does not indicate
- * a canon tag, or if this `Tag` is not of a kind which can be placed
- * in canon.
- */
- addInCanonTag(...tags) {
- const storage = this.#storage;
- const kind = this.#kind;
- const inCanon = this.#data.inCanon;
- if (!HAS_IN_CANON.has(kind)) {
- // ☡ This is not an entity tag, setting tag, or recognized
- // subclass.
- throw new TypeError(
- `Cannot put Tag in canon: Incorrect Tag type: ${kind}.`,
- );
- } else {
- // This has a kind which can be placed in canon.
- for (const $ of tags) {
- // Iterate over each tag and attempt to set this `Tag` in canon
- // of it.
- const identifier = toIdentifier($);
- if (identifier == null) {
- // ☡ The current tag has no identifier.
- throw new TypeError(
- "Cannot put Tag in canon: Identifier must not be nullish.",
- );
- } else if (inCanon.has(identifier)) {
- // Short‐circuit: The identifier is already something this
- // `Tag` is in canon of.
- /* do nothing */
- } else {
- // The current tag has an identifier.
- const tag = storage.get(identifier);
- if (tag == null) {
- // ☡ The current tag has not been persisted to this `Tag`’s
- // storage.
- throw new RangeError(
- `Cannot put Tag in canon: Identifier is not persisted: ${identifier}.`,
- );
- } else if (
- // ※ If the first check succeeds, then the current tag
- // must have `Tag` private class features.
- !this.#isTagInStorage(tag) || tag.#kind != "CanonTag"
- ) {
- // ☡ The current tag is not a canon tag in the correct
- // tag system.
- throw new TypeError(
- `Cannot put Tag in canon: Tags can only be in Canon Tags from the same Tag System, but got: ${identifier}.`,
- );
- } else {
- // The current tag is a canon tag in the correct tag
- // system; add its identifier.
- inCanon.add(identifier);
- }
- }
- }
- }
- return this;
- }
-
- /**
- * Adds the provided tags to the list of tags that this `Tag`
- * involves, then returns this `Tag`.
- *
- * Arguments may be string identifiers or objects with an
- * `.identifier` property.
- *
- * ☡ This method will throw if this `Tag` is not a conceptual tag, or
- * if this `Tag` is a relationship tag and a provided argument does
- * not indicate a character or relationship tag.
- */
- addInvolvesTag(...tags) {
- const storage = this.#storage;
- const kind = this.#kind;
- const involves = this.#data.involves;
- if (!CONCEPTUAL_TAG_KINDS.has(kind)) {
- // ☡ This is not a conceptual tag or recognized subclass.
- throw new TypeError(
- `Cannot involve Tag: Incorrect Tag type: ${kind}.`,
- );
- } else {
- // This is a conceptual tag.
- for (const $ of tags) {
- // Iterate over each tag and attempt to set this `Tag` as
- // involving it.
- const identifier = toIdentifier($);
- if (identifier == null) {
- // ☡ The current tag has no identifier.
- throw new TypeError(
- "Cannot involve Tag: Identifier must not be nullish.",
- );
- } else if (involves.has(identifier)) {
- // Short‐circuit: The identifier is already something this
- // `Tag` involves.
- /* do nothing */
- } else {
- // The current tag has an identifier.
- const tag = storage.get(identifier);
- if (tag == null) {
- // ☡ The current tag has not been persisted to this `Tag`’s
- // storage.
- throw new RangeError(
- `Cannot involve Tag: Identifier is not persisted: ${identifier}.`,
- );
- } else if (
- // ※ If the first check succeeds, then the current tag
- // must have `Tag` private class features.
- !this.#isTagInStorage(tag) ||
- RELATIONSHIP_TAG_KINDS.has(kind) &&
- !INVOLVABLE_IN_RELATIONSHIP.has(tag.#kind)
- ) {
- // ☡ The current tag is in the correct tag system and
- // includable.
- throw new TypeError(
- `Cannot involve Tag: Tags must be the same Tag System and involvable, but got: ${identifier}.`,
- );
- } else {
- // The current tag is an involvable tag in the correct tag
- // system; add its identifier.
- involves.add(identifier);
- }
- }
- }
- }
- return this;
- }
-
- /** Yields the alternative labels of this `Tag`. */
- *altLabels() {
- yield* this.#data.altLabel;
- }
-
- /** Returns the authority (domain) name for this `Tag`. */
- get authorityName() {
- return this.#system.authorityName;
- }
-
- /** Yields `Tag`s which are broader than this `Tag`. */
- *broaderTags() {
- const storage = this.#storage;
- for (const identifier of this.#data.broader) {
- // Iterate over the broader tags and yield them if possible.
- const tag = storage.get(identifier);
- if (!this.#isTagInStorage(tag)) {
- // The broader tag no longer appears in storage; perhaps it was
- // deleted.
- /* do nothing */
- } else {
- // The broader tag exists and is constructable from storage.
- yield tag;
- }
- }
- }
-
- /** Yields `Tag`s which are broader than this `Tag`, transitively. */
- *broaderTransitiveTags() {
- const storage = this.#storage;
- const encountered = new Set();
- let pending = new Set(this.#data.broader);
- while (pending.size > 0) {
- // Loop until all broader tags have been encountered.
- const processing = pending;
- pending = new Set();
- for (const identifier of processing) {
- // Iterate over the broader tags and yield them if possible.
- if (!encountered.has(identifier)) {
- // The broader tag has not been encountered before.
- encountered.add(identifier);
- const tag = storage.get(identifier);
- if (!this.#isTagInStorage(tag)) {
- // The broader tag no longer appears in storage; perhaps it
- // was deleted.
- /* do nothing */
- } else {
- // The broader tag exists and is constructable from
- // storage.
- yield tag;
- for (const transitive of tag.#data.broader) {
- // Iterate over the broader tags of the current broader
- // tag and add them to pending as needed.
- if (!encountered.has(transitive)) {
- // The broader broader tag has not been encountered
- // yet.
- pending.add(transitive);
- } else {
- // The broader broader tag has already been
- // encountered.
- /* do nothing */
- }
- }
- }
- } else {
- // The broader tag has already been encountered.
- /* do nothing */
- }
- }
- }
- }
-
- /**
- * Removes the provided string label(s) from this `Tag` as alternate
- * labels, then returns this `Tag`.
- */
- deleteAltLabel(...labels) {
- const altLabels = this.#data.altLabel;
- let objectLabels = null; // initialized on first use
- for (const $ of labels) {
- // Iterate over each provided label and attempt to remove it.
- const literal = langString($);
- if (Object(literal) === literal) {
- // The current label is a language‐tagged string.
- objectLabels ??= [...function* () {
- for (const altLabel of altLabels) {
- // Iterate over the existing labels and yield the
- // language‐tagged strings.
- if (Object(altLabel) === altLabel) {
- // The current existing label is a language‐tagged
- // string.
- yield altLabel;
- } else {
- // The current existing label is not a language‐tagged
- // string.
- /* do nothing */
- }
- }
- }()];
- const existing = objectLabels.find((objectLabel) =>
- objectLabel["@value"] == literal["@value"] &&
- objectLabel["@language"] == literal["@language"]
- );
- altLabels.delete(existing);
- } else {
- // The current label is a simple string.
- altLabels.delete(literal);
- }
- }
- return this;
- }
-
- /**
- * Removes the provided tags from the list of tags that this `Tag` is
- * narrower than, then returns this `Tag`.
- *
- * Arguments may be string identifiers or objects with an
- * `.identifier` property.
- */
- deleteBroaderTag(...tags) {
- const broader = this.#data.broader;
- for (const $ of tags) {
- // Iterate over the provided tags and delete them.
- broader.delete(toIdentifier($));
- }
- return this;
- }
-
- /**
- * Removes the provided string label(s) from this `Tag` as hidden
- * labels, then returns this `Tag`.