UNPKG

11 kBPlain TextView Raw
1/**
2 * Copyright 2020 Inrupt Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to use,
7 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8 * Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22import { NamedNode, Literal, Quad } from "rdf-js";
23import { DataFactory } from "./rdfjs";
24import { IriString, LocalNode, Iri } from "./interfaces";
25
26/**
27 * IRIs of the XML Schema data types we support
28 * @internal
29 */
30export const xmlSchemaTypes = {
31 boolean: "http://www.w3.org/2001/XMLSchema#boolean",
32 dateTime: "http://www.w3.org/2001/XMLSchema#dateTime",
33 decimal: "http://www.w3.org/2001/XMLSchema#decimal",
34 integer: "http://www.w3.org/2001/XMLSchema#integer",
35 string: "http://www.w3.org/2001/XMLSchema#string",
36 langString: "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString",
37} as const;
38/** @internal */
39export type XmlSchemaTypeIri = typeof xmlSchemaTypes[keyof typeof xmlSchemaTypes];
40
41/**
42 * @internal
43 * @param value Value to serialise.
44 * @returns String representation of `value`.
45 */
46export function serializeBoolean(value: boolean): string {
47 return value ? "1" : "0";
48}
49/**
50 * @internal
51 * @param value Value to deserialise.
52 * @returns Deserialized boolean, or null if the given value is not a valid serialised boolean.
53 */
54export function deserializeBoolean(value: string): boolean | null {
55 if (value === "1") {
56 return true;
57 } else if (value === "0") {
58 return false;
59 } else {
60 return null;
61 }
62}
63
64/**
65 * @internal
66 * @param value Value to serialise.
67 * @returns String representation of `value`.
68 */
69export function serializeDatetime(value: Date): string {
70 // To align with rdflib, we ignore miliseconds:
71 // https://github.com/linkeddata/rdflib.js/blob/d84af88f367b8b5f617c753d8241c5a2035458e8/src/literal.js#L74
72 const roundedDate = new Date(
73 Date.UTC(
74 value.getUTCFullYear(),
75 value.getUTCMonth(),
76 value.getUTCDate(),
77 value.getUTCHours(),
78 value.getUTCMinutes(),
79 value.getUTCSeconds(),
80 0
81 )
82 );
83 // Truncate the `.000Z` at the end (i.e. the miliseconds), to plain `Z`:
84 const rdflibStyleString = roundedDate.toISOString().replace(/\.000Z$/, "Z");
85 return rdflibStyleString;
86}
87/**
88 * @internal
89 * @param value Value to deserialise.
90 * @returns Deserialized datetime, or null if the given value is not a valid serialised datetime.
91 */
92export function deserializeDatetime(literalString: string): Date | null {
93 if (
94 literalString === null ||
95 literalString.length <= 17 ||
96 literalString.indexOf("Z") === -1
97 ) {
98 return null;
99 }
100
101 // See https://github.com/linkeddata/rdflib.js/blob/d84af88f367b8b5f617c753d8241c5a2035458e8/src/literal.js#L87
102 const utcFullYear = parseInt(literalString.substring(0, 4), 10);
103 const utcMonth = parseInt(literalString.substring(5, 7), 10) - 1;
104 const utcDate = parseInt(literalString.substring(8, 10), 10);
105 const utcHours = parseInt(literalString.substring(11, 13), 10);
106 const utcMinutes = parseInt(literalString.substring(14, 16), 10);
107 const utcSeconds = parseInt(
108 literalString.substring(17, literalString.indexOf("Z")),
109 10
110 );
111 const date = new Date(0);
112 date.setUTCFullYear(utcFullYear);
113 date.setUTCMonth(utcMonth);
114 date.setUTCDate(utcDate);
115 date.setUTCHours(utcHours);
116 date.setUTCMinutes(utcMinutes);
117 date.setUTCSeconds(utcSeconds);
118 return date;
119}
120
121/**
122 * @internal
123 * @param value Value to serialise.
124 * @returns String representation of `value`.
125 */
126export function serializeDecimal(value: number): string {
127 return value.toString();
128}
129/**
130 * @internal
131 * @param value Value to deserialise.
132 * @returns Deserialized decimal, or null if the given value is not a valid serialised decimal.
133 */
134export function deserializeDecimal(literalString: string): number | null {
135 const deserialized = Number.parseFloat(literalString);
136 if (Number.isNaN(deserialized)) {
137 return null;
138 }
139 return deserialized;
140}
141
142/**
143 * @internal
144 * @param value Value to serialise.
145 * @returns String representation of `value`.
146 */
147export function serializeInteger(value: number): string {
148 return value.toString();
149}
150/**
151 * @internal
152 * @param value Value to deserialise.
153 * @returns Deserialized integer, or null if the given value is not a valid serialised integer.
154 */
155export function deserializeInteger(literalString: string): number | null {
156 const deserialized = Number.parseInt(literalString, 10);
157 if (Number.isNaN(deserialized)) {
158 return null;
159 }
160 return deserialized;
161}
162
163/**
164 * @internal
165 * @param locale Locale to transform into a consistent format.
166 */
167export function normalizeLocale(locale: string): string {
168 return locale.toLowerCase();
169}
170
171/**
172 * @internal Library users shouldn't need to be exposed to raw NamedNodes.
173 * @param value The value that might or might not be a Named Node.
174 * @returns Whether `value` is a Named Node.
175 */
176export function isNamedNode<T>(value: T | NamedNode): value is NamedNode {
177 return (
178 typeof value === "object" &&
179 typeof (value as NamedNode).termType === "string" &&
180 (value as NamedNode).termType === "NamedNode"
181 );
182}
183
184/**
185 * @internal Library users shouldn't need to be exposed to raw Literals.
186 * @param value The value that might or might not be a Literal.
187 * @returns Whether `value` is a Literal.
188 */
189export function isLiteral<T>(value: T | Literal): value is Literal {
190 return (
191 typeof value === "object" &&
192 typeof (value as Literal).termType === "string" &&
193 (value as Literal).termType === "Literal"
194 );
195}
196
197/**
198 * @internal Library users shouldn't need to be exposed to LocalNodes.
199 * @param value The value that might or might not be a Node with no known IRI yet.
200 * @returns Whether `value` is a Node with no known IRI yet.
201 */
202export function isLocalNode<T>(value: T | LocalNode): value is LocalNode {
203 return (
204 typeof value === "object" &&
205 typeof (value as LocalNode).termType === "string" &&
206 (value as LocalNode).termType === "BlankNode" &&
207 typeof (value as LocalNode).name === "string"
208 );
209}
210
211/**
212 * Construct a new LocalNode.
213 *
214 * @internal Library users shouldn't need to be exposed to LocalNodes.
215 * @param name Name to identify this node by.
216 * @returns A LocalNode whose name will be resolved when it is persisted to a Pod.
217 */
218export function getLocalNode(name: string): LocalNode {
219 const localNode: LocalNode = Object.assign(DataFactory.blankNode(), {
220 name: name,
221 });
222 return localNode;
223}
224
225/**
226 * Ensure that a given value is a Named Node.
227 *
228 * If the given parameter is a Named Node already, it will be returned as-is. If it is a string, it
229 * will check whether it is a valid IRI. If not, it will throw an error; otherwise a Named Node
230 * representing the given IRI will be returned.
231 *
232 * @internal Library users shouldn't need to be exposed to raw NamedNodes.
233 * @param iri The IRI that should be converted into a Named Node, if it isn't one yet.
234 */
235export function asNamedNode(iri: Iri | IriString): NamedNode {
236 if (isNamedNode(iri)) {
237 return iri;
238 }
239 // If the runtime environment supports URL, instantiate one.
240 // If thte given IRI is not a valid URL, it will throw an error.
241 // See: https://developer.mozilla.org/en-US/docs/Web/API/URL
242 /* istanbul ignore else [URL is available in our testing environment, so we cannot test the alternative] */
243 if (typeof URL !== "undefined") {
244 new URL(iri);
245 }
246 return DataFactory.namedNode(iri);
247}
248
249interface IsEqualOptions {
250 resourceIri?: IriString;
251}
252/**
253 * Check whether two current- or potential NamedNodes are/will be equal.
254 *
255 * @internal Utility method; library users should not need to interact with LocalNodes directly.
256 */
257export function isEqual(
258 node1: NamedNode | LocalNode,
259 node2: NamedNode | LocalNode,
260 options: IsEqualOptions = {}
261): boolean {
262 if (isNamedNode(node1) && isNamedNode(node2)) {
263 return node1.equals(node2);
264 }
265 if (isLocalNode(node1) && isLocalNode(node2)) {
266 return node1.name === node2.name;
267 }
268 if (typeof options.resourceIri === "undefined") {
269 // If we don't know what IRI to resolve the LocalNode to,
270 // we cannot conclude that it is equal to the NamedNode's full IRI:
271 return false;
272 }
273 const namedNode1 = isNamedNode(node1)
274 ? node1
275 : resolveIriForLocalNode(node1, options.resourceIri);
276 const namedNode2 = isNamedNode(node2)
277 ? node2
278 : resolveIriForLocalNode(node2, options.resourceIri);
279 return namedNode1.equals(namedNode2);
280}
281
282/**
283 * @internal Utility method; library users should not need to interact with LocalNodes directly.
284 * @param quad The Quad to resolve LocalNodes in.
285 * @param resourceIri The IRI of the Resource to resolve the LocalNodes against.
286 */
287export function resolveIriForLocalNodes(
288 quad: Quad,
289 resourceIri: IriString
290): Quad {
291 const subject = isLocalNode(quad.subject)
292 ? resolveIriForLocalNode(quad.subject, resourceIri)
293 : quad.subject;
294 const object = isLocalNode(quad.object)
295 ? resolveIriForLocalNode(quad.object, resourceIri)
296 : quad.object;
297 return {
298 ...quad,
299 subject: subject,
300 object: object,
301 };
302}
303
304/**
305 * @internal Utility method; library users should not need to interact with LocalNodes directly.
306 * @param localNode The LocalNode to resolve to a NamedNode.
307 * @param resourceIri The Resource in which the Node will be saved.
308 */
309export function resolveIriForLocalNode(
310 localNode: LocalNode,
311 resourceIri: IriString
312): NamedNode {
313 return DataFactory.namedNode(resolveLocalIri(localNode.name, resourceIri));
314}
315
316/**
317 * @internal API for internal use only.
318 * @param name The name identifying a Thing.
319 * @param resourceIri The Resource in which the Thing can be found.
320 */
321export function resolveLocalIri(
322 name: string,
323 resourceIri: IriString
324): IriString {
325 /* istanbul ignore if [The URL interface is available in the testing environment, so we cannot test this] */
326 if (typeof URL === "undefined") {
327 throw new Error(
328 "The URL interface is not available, so an IRI cannot be determined."
329 );
330 }
331 const thingIri = new URL(resourceIri);
332 thingIri.hash = name;
333 return thingIri.href;
334}