// SPDX-FileCopyrightText: 2026 Kerstin Humm // // SPDX-License-Identifier: AGPL-3.0-or-later import { AnnotatedJson, Annotation, isAnnotatedObject, isJsonObject, mergeAnnotatedJson, Rule, } from "../types.js"; import { buildReference } from "../utils.js"; import * as htmlparser2 from "htmlparser2"; // @ts-ignore import * as language_tags from "language-tags"; export default { name: "name attribute", validate: (self) => { let containsHtml = (str: string) => { const doc = htmlparser2.parseDocument(str); return doc.children.some((node) => node.type === "tag"); }; let checkNameValue = (name: string): Annotation => { if (name === "") { return { kind: "Violation", id: "NameEmpty", text: `The name field with value ${self["name"]} is an empty string.`, reference: buildReference({ anchor: "object-type-event", text: "Values for the name MUST NOT be an empty string", }), }; } if (containsHtml(name)) { return { kind: "Violation", id: "NameContainsHtml", text: `The name field with value ${self["name"]} contains HTML.`, reference: buildReference({ anchor: "object-type-event", text: "HTML markup MUST NOT be included.", }), }; } else { return { kind: "Correct", text: "Valid name", }; } }; if (isJsonObject(self["name"])) { let result: { annotations: Annotation[]; object: { [key: string]: AnnotatedJson }; } = { annotations: [], object: {} }; for (const [languageTag, name] of Object.entries(self["name"])) { if (!language_tags.check(languageTag)) { const merge = mergeAnnotatedJson(result, { annotations: [], object: { "name": { annotations: [], object: { [languageTag]: { kind: "Violation", id: "NameInvalidLanguageTag", text: `The name.${languageTag} attribute is not a valid IETF BCP 47 language tag`, reference: buildReference({ anchor: "object-type-event", text: "the name MAY be expressed using multiple language-tagged values.", }), }, }, }, }, }); if (merge.ok) { if (isAnnotatedObject(merge.value)) { result = merge.value; } } else { throw new Error(merge.error); } } else if (typeof name === "string") { const merge = mergeAnnotatedJson(result, { annotations: [], object: { "name": { annotations: [], object: { [languageTag]: checkNameValue(name), }, }, }, }); if (merge.ok) { if (isAnnotatedObject(merge.value)) { result = merge.value; } } else { throw new Error(merge.error); } } else { // TODO check that value can only be string } } return result; } else if (typeof self["name"] === "string") { return { annotations: [], object: { "name": checkNameValue(self["name"]) }, }; } else { return { annotations: [], object: { "name": { kind: "Violation", id: "InvalidType", text: "name attribute has invalid type", }, }, }; } }, tests: [ { value: { "name": "" }, result: { annotations: [], object: { "name": "NameEmpty" } }, }, { value: { "name": "Title" }, result: { annotations: [], object: { "name": "NameContainsHtml" } }, }, { value: { "name": "Title" }, result: { annotations: [], object: {} }, }, { value: { "name": { "en": "Title", "de": "Titel" } }, result: { annotations: [], object: { "name": { annotations: [], object: { "en": "Correct", "de": "Correct" }, }, }, }, }, { value: { "name": { "en": "Title", "invalid": "Titel" } }, result: { annotations: [], object: { "name": { annotations: [], object: { "en": "Correct", "invalid": "NameInvalidLanguageTag" }, }, }, }, }, ], } satisfies Rule;