0
# Utility Services
1
2
Supporting interfaces for HTML sanitization, URL resolution, LaTeX typesetting, and Markdown parsing.
3
4
## Capabilities
5
6
### HTML Sanitization
7
8
#### ISanitizer Interface
9
10
Handles HTML sanitization to ensure safe rendering of untrusted content.
11
12
```typescript { .api }
13
/**
14
* An object that handles html sanitization
15
*/
16
interface ISanitizer {
17
/** Whether to replace URLs by HTML anchors */
18
getAutolink?(): boolean;
19
/**
20
* Sanitize an HTML string
21
* @param dirty - The dirty text
22
* @param options - The optional sanitization options
23
* @returns The sanitized string
24
*/
25
sanitize(dirty: string, options?: ISanitizerOptions): string;
26
/** Whether to allow name and id properties */
27
readonly allowNamedProperties?: boolean;
28
}
29
```
30
31
#### ISanitizerOptions Interface
32
33
Configuration options for HTML sanitization.
34
35
```typescript { .api }
36
/**
37
* The options used to sanitize
38
*/
39
interface ISanitizerOptions {
40
/** The allowed tags */
41
allowedTags?: string[];
42
/** The allowed attributes for a given tag */
43
allowedAttributes?: { [key: string]: string[] };
44
/** The allowed style values for a given tag */
45
allowedStyles?: { [key: string]: { [key: string]: RegExp[] } };
46
}
47
```
48
49
**Usage Example:**
50
51
```typescript
52
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
53
54
class CustomRenderer implements IRenderMime.IRenderer {
55
constructor(private options: IRenderMime.IRendererOptions) {}
56
57
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
58
const htmlContent = model.data['text/html'] as string;
59
60
// Custom sanitization options for this renderer
61
const sanitizeOptions: IRenderMime.ISanitizerOptions = {
62
allowedTags: ['div', 'span', 'p', 'strong', 'em', 'a', 'img'],
63
allowedAttributes: {
64
'a': ['href', 'title'],
65
'img': ['src', 'alt', 'width', 'height'],
66
'div': ['class', 'style']
67
},
68
allowedStyles: {
69
'div': {
70
'color': [/^#[0-9a-f]{6}$/i],
71
'background-color': [/^#[0-9a-f]{6}$/i],
72
'font-size': [/^\d+px$/]
73
}
74
}
75
};
76
77
// Sanitize the HTML
78
const sanitizedHtml = this.options.sanitizer.sanitize(htmlContent, sanitizeOptions);
79
80
// Check if autolink is enabled
81
const hasAutolink = this.options.sanitizer.getAutolink?.() ?? false;
82
if (hasAutolink) {
83
console.log('URLs will be automatically converted to links');
84
}
85
86
this.node.innerHTML = sanitizedHtml;
87
}
88
}
89
```
90
91
### URL Resolution
92
93
#### IResolver Interface
94
95
Resolves relative URLs and handles file path resolution within JupyterLab.
96
97
```typescript { .api }
98
/**
99
* An object that resolves relative URLs
100
*/
101
interface IResolver {
102
/** Resolve a relative url to an absolute url path */
103
resolveUrl(url: string): Promise<string>;
104
/**
105
* Get the download url for a given absolute url path.
106
* This URL may include a query parameter.
107
*/
108
getDownloadUrl(url: string): Promise<string>;
109
/**
110
* Whether the URL should be handled by the resolver or not.
111
* This is similar to the `isLocal` check in `URLExt`,
112
* but can also perform additional checks on whether the
113
* resolver should handle a given URL.
114
* @param allowRoot - Whether the paths starting at Unix-style filesystem root (`/`) are permitted
115
*/
116
isLocal?(url: string, allowRoot?: boolean): boolean;
117
/**
118
* Resolve a path from Jupyter kernel to a path:
119
* - relative to `root_dir` (preferably) this is in jupyter-server scope,
120
* - path understood and known by kernel (if such a path exists).
121
* Returns `null` if there is no file matching provided path in neither
122
* kernel nor jupyter-server contents manager.
123
*/
124
resolvePath?(path: string): Promise<IResolvedLocation | null>;
125
}
126
```
127
128
#### IResolvedLocation Interface
129
130
Represents a resolved file location with scope information.
131
132
```typescript { .api }
133
interface IResolvedLocation {
134
/** Location scope */
135
scope: 'kernel' | 'server';
136
/** Resolved path */
137
path: string;
138
}
139
```
140
141
**Usage Example:**
142
143
```typescript
144
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
145
146
class ImageRenderer implements IRenderMime.IRenderer {
147
constructor(private options: IRenderMime.IRendererOptions) {}
148
149
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
150
const imagePath = model.data['image/path'] as string;
151
const resolver = this.options.resolver;
152
153
if (resolver) {
154
try {
155
// Check if this is a local path we should handle
156
const isLocal = resolver.isLocal?.(imagePath) ?? true;
157
158
if (isLocal) {
159
// Resolve relative path to absolute
160
const absoluteUrl = await resolver.resolveUrl(imagePath);
161
162
// Get download URL for the image
163
const downloadUrl = await resolver.getDownloadUrl(absoluteUrl);
164
165
// Resolve kernel path if needed
166
const resolved = await resolver.resolvePath?.(imagePath);
167
if (resolved) {
168
console.log(`Image resolved to ${resolved.scope}: ${resolved.path}`);
169
}
170
171
// Create image element with resolved URL
172
const img = document.createElement('img');
173
img.src = downloadUrl;
174
img.alt = 'Resolved image';
175
this.node.appendChild(img);
176
}
177
} catch (error) {
178
console.error('Failed to resolve image path:', error);
179
this.node.textContent = `Failed to load image: ${imagePath}`;
180
}
181
}
182
}
183
}
184
```
185
186
### Link Handling
187
188
#### ILinkHandler Interface
189
190
Handles click events on links within rendered content.
191
192
```typescript { .api }
193
/**
194
* An object that handles links on a node
195
*/
196
interface ILinkHandler {
197
/**
198
* Add the link handler to the node
199
* @param node the anchor node for which to handle the link
200
* @param path the path to open when the link is clicked
201
* @param id an optional element id to scroll to when the path is opened
202
*/
203
handleLink(node: HTMLElement, path: string, id?: string): void;
204
/**
205
* Add the path handler to the node
206
* @param node the anchor node for which to handle the link
207
* @param path the path to open when the link is clicked
208
* @param scope the scope to which the path is bound
209
* @param id an optional element id to scroll to when the path is opened
210
*/
211
handlePath?(node: HTMLElement, path: string, scope: 'kernel' | 'server', id?: string): void;
212
}
213
```
214
215
**Usage Example:**
216
217
```typescript
218
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
219
220
class LinkEnabledRenderer implements IRenderMime.IRenderer {
221
constructor(private options: IRenderMime.IRendererOptions) {}
222
223
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
224
const htmlContent = model.data['text/html'] as string;
225
const sanitized = this.options.sanitizer.sanitize(htmlContent);
226
this.node.innerHTML = sanitized;
227
228
// Setup link handling
229
if (this.options.linkHandler) {
230
this.setupLinkHandling();
231
}
232
}
233
234
private setupLinkHandling(): void {
235
const links = this.node.querySelectorAll('a[href]');
236
237
links.forEach(link => {
238
const href = link.getAttribute('href')!;
239
const linkElement = link as HTMLElement;
240
241
// Handle different types of links
242
if (href.startsWith('#')) {
243
// Fragment link - scroll to element
244
const elementId = href.substring(1);
245
this.options.linkHandler!.handleLink(linkElement, '', elementId);
246
} else if (href.startsWith('/')) {
247
// Absolute path - specify scope
248
this.options.linkHandler!.handlePath?.(linkElement, href, 'server');
249
} else if (!href.startsWith('http')) {
250
// Relative path
251
this.options.linkHandler!.handleLink(linkElement, href);
252
}
253
// External links (http/https) are handled by default browser behavior
254
});
255
}
256
}
257
```
258
259
### LaTeX Typesetting
260
261
#### ILatexTypesetter Interface
262
263
Handles LaTeX mathematical expression rendering.
264
265
```typescript { .api }
266
/**
267
* The interface for a LaTeX typesetter
268
*/
269
interface ILatexTypesetter {
270
/**
271
* Typeset a DOM element.
272
* The typesetting may happen synchronously or asynchronously.
273
* @param element - the DOM element to typeset
274
*/
275
typeset(element: HTMLElement): void;
276
}
277
```
278
279
**Usage Example:**
280
281
```typescript
282
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
283
284
class MathRenderer implements IRenderMime.IRenderer {
285
constructor(private options: IRenderMime.IRendererOptions) {}
286
287
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
288
const mathContent = model.data['text/latex'] as string;
289
290
// Create container for math content
291
const mathContainer = document.createElement('div');
292
mathContainer.className = 'math-content';
293
mathContainer.textContent = mathContent;
294
295
this.node.appendChild(mathContainer);
296
297
// Typeset LaTeX if typesetter is available
298
if (this.options.latexTypesetter) {
299
this.options.latexTypesetter.typeset(this.node);
300
}
301
}
302
}
303
```
304
305
### Markdown Parsing
306
307
#### IMarkdownParser Interface
308
309
Converts Markdown text to HTML.
310
311
```typescript { .api }
312
/**
313
* The interface for a Markdown parser
314
*/
315
interface IMarkdownParser {
316
/**
317
* Render a markdown source into unsanitized HTML
318
* @param source - The string to render
319
* @returns A promise of the string containing HTML which may require sanitization
320
*/
321
render(source: string): Promise<string>;
322
}
323
```
324
325
**Usage Example:**
326
327
```typescript
328
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
329
330
class MarkdownRenderer implements IRenderMime.IRenderer {
331
constructor(private options: IRenderMime.IRendererOptions) {}
332
333
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
334
const markdownSource = model.data['text/markdown'] as string;
335
336
if (this.options.markdownParser) {
337
try {
338
// Parse markdown to HTML
339
const rawHtml = await this.options.markdownParser.render(markdownSource);
340
341
// Sanitize the resulting HTML
342
const sanitizedHtml = this.options.sanitizer.sanitize(rawHtml);
343
344
this.node.innerHTML = sanitizedHtml;
345
346
// Apply LaTeX typesetting if available
347
if (this.options.latexTypesetter) {
348
this.options.latexTypesetter.typeset(this.node);
349
}
350
351
// Setup link handling if available
352
if (this.options.linkHandler) {
353
this.setupLinks();
354
}
355
356
} catch (error) {
357
console.error('Failed to render markdown:', error);
358
this.node.textContent = 'Failed to render markdown content';
359
}
360
} else {
361
// Fallback: display raw markdown
362
const pre = document.createElement('pre');
363
pre.textContent = markdownSource;
364
this.node.appendChild(pre);
365
}
366
}
367
368
private setupLinks(): void {
369
const links = this.node.querySelectorAll('a[href]');
370
links.forEach(link => {
371
const href = link.getAttribute('href')!;
372
this.options.linkHandler!.handleLink(link as HTMLElement, href);
373
});
374
}
375
}
376
```