import ClassOrder from './class-order' import RDFlibNamedNode from './named-node' import Node from './node-internal' import { FromValueReturns, LiteralTermType, ValueType } from './types' import { isLiteral } from './utils/terms' import XSD from './xsd-internal' import { Literal as TFLiteral, Term } from './tf-types' /** * An RDF literal, containing some value which isn't expressed as an IRI. * @link https://rdf.js.org/data-model-spec/#literal-interface */ export default class Literal extends Node implements TFLiteral { termType: typeof LiteralTermType = LiteralTermType classOrder = ClassOrder.Literal /** * The literal's datatype as a named node */ datatype: RDFlibNamedNode = XSD.string isVar = 0 /** * The language for the literal */ language: string = '' /** * Initializes a literal * @param value - The literal's lexical value * @param language - The language for the literal. Defaults to ''. * @param datatype - The literal's datatype as a named node. Defaults to xsd:string. */ constructor (value: string, language?: string | null, datatype?) { super(value) if (language) { this.language = language this.datatype = XSD.langString } else if (datatype) { this.datatype = RDFlibNamedNode.fromValue(datatype) } else { this.datatype = XSD.string } } /** * Gets a copy of this literal */ copy (): Literal { return new Literal(this.value, this.lang, this.datatype) } /** * Gets whether two literals are the same * @param other The other statement */ equals (other: Term): boolean { if (!other) { return false } return (this.termType === other.termType) && (this.value === other.value) && (this.language === (other as Literal).language) && ((!this.datatype && !(other as Literal).datatype) || (this.datatype && this.datatype.equals((other as Literal).datatype))) } /** * The language for the literal * @deprecated use {language} instead */ get lang (): string { return this.language } set lang (language: string) { this.language = language || '' } toNT(): string { return Literal.toNT(this) } /** Serializes a literal to an N-Triples string */ static toNT (literal: Literal): string { if (typeof literal.value === 'number') { return '' + literal.value } else if (typeof literal.value !== 'string') { throw new Error('Value of RDF literal is not string or number: ' + literal.value) } var str = literal.value // #x22 ("), #x5C (\), #x0A (\n) and #xD (\r) are disallowed and need to be replaced // see https://www.w3.org/TR/n-triples/#grammar-production-STRING_LITERAL_QUOTE str = str.replace(/\\/g, '\\\\') str = str.replace(/\"/g, '\\"') str = str.replace(/\n/g, '\\n') str = str.replace(/\r/g, '\\r') str = '"' + str + '"' if (literal.language) { str += '@' + literal.language } else if (!literal.datatype.equals(XSD.string)) { // Only add datatype if it's not a string str += '^^' + literal.datatype.toCanonical() } return str } toString () { return '' + this.value } /** * Builds a literal node from a boolean value * @param value - The value */ static fromBoolean (value: boolean): Literal { let strValue = value ? '1' : '0' return new Literal(strValue, null, XSD.boolean) } /** * Builds a literal node from a date value * @param value The value */ static fromDate(value: Date): Literal { if (!(value instanceof Date)) { throw new TypeError('Invalid argument to Literal.fromDate()') } let d2 = function (x) { return ('' + (100 + x)).slice(1, 3) } let date = '' + value.getUTCFullYear() + '-' + d2(value.getUTCMonth() + 1) + '-' + d2(value.getUTCDate()) + 'T' + d2(value.getUTCHours()) + ':' + d2(value.getUTCMinutes()) + ':' + d2(value.getUTCSeconds()) + 'Z' return new Literal(date, null, XSD.dateTime) } /** * Builds a literal node from a number value * @param value - The value */ static fromNumber(value: number): Literal { if (typeof value !== 'number') { throw new TypeError('Invalid argument to Literal.fromNumber()') } let datatype: RDFlibNamedNode const strValue = value.toString() if (strValue.indexOf('e') < 0 && Math.abs(value) <= Number.MAX_SAFE_INTEGER) { datatype = Number.isInteger(value) ? XSD.integer : XSD.decimal } else { datatype = XSD.double } return new Literal(strValue, null, datatype) } /** * Builds a literal node from an input value * @param value - The input value */ static fromValue(value: ValueType): T { if (isLiteral(value)) { return value as unknown as T } switch (typeof value) { case 'object': if (value instanceof Date) { return Literal.fromDate(value) as unknown as T } case 'boolean': return Literal.fromBoolean(value as boolean) as unknown as T case 'number': return Literal.fromNumber(value as number) as unknown as T case 'string': return new Literal(value) as unknown as T } throw new Error("Can't make literal from " + value + ' of type ' + typeof value) } }