Convert ANSI SGR (color/style) escape sequences to appropriately-styled HTML spans in TypeScript.
Import sgrp from JSR and use it in your code. Example Deno program to convert colored terminal output from stdin to HTML on stdout:
import { SGRToStringTransformer } from "jsr:@mkuranowski/sgrp";
await Deno.stdin.readable
.pipeThrough(new TextDecoderStream())
.pipeThrough((new SGRToStringTransformer()).toStream())
.pipeThrough(new TransformStream({
start: (controller) => controller.enqueue("<!DOCTYPE html><html><body><pre>"),
flush: (controller) => controller.enqueue("</pre></body></html>"),
}))
.pipeThrough(new TextEncoderStream())
.pipeTo(Deno.stdout.writable);Download sgrp.js Javascript module
from GitHub releases and put it in your server. This example below will load a colored terminal
output file provided by the user and render it in a <pre> container:
<!DOCTYPE html>
<html>
<body>
<h3>Upload console output:</h3>
<input type="file" id="fileSelector" />
<h3>Rendered HTML:</h3>
<pre id="outputElement"></pre>
</body>
<script type="module">
import { SGRToElementSink } from "./sgrp.js";
const fileSelector = document.getElementById("fileSelector");
const outputElement = document.getElementById("outputElement");
fileSelector.addEventListener("change", async () => {
outputElement.innerText = "";
if (fileSelector.files !== null && fileSelector.files.length > 0) {
await fileSelector.files[0].stream()
.pipeThrough(new TextDecoderStream())
.pipeTo((new SGRToElementSink(outputElement)).toStream());
}
});
</script>
</html>sgrp only "consumes" Select Graphic Rendition ANSI escape sequences. Any other ANSI escape sequence is passed as-is.
Only the following SGRs are supported. Everything else is silently ignored:
| Number | Name | Applied CSS | Overwritten by |
|---|---|---|---|
| 0 | Reset | (everything removed) | (anything other than 0) |
| 1 | Bold | font-weight: bolder | 0, 2, 22 |
| 2 | Faint | font-weight: lighter | 0, 1, 22 |
| 3 | Italic | font-style: italic | 0, 23 |
| 4 | Underline | text-decoration: underline | 0, 24 |
| 9 | Crossed out | text-decoration: line-through | 0, 29 |
| 22 | Normal intensity | removed font-weight | 1, 2 |
| 23 | Not italic | removed font-style | 3 |
| 24 | Not underlined | removed underline from text-decoration | 4 |
| 29 | Not crossed out | removed line-through from text-decoration | 9 |
| 30 | foreground black | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 31 | fg red | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 32 | fg green | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 33 | fg yellow | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 34 | fg blue | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 35 | fg magenta | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 36 | fg cyan | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 37 | fg white | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 38 | fg custom | set color; see below for details | 0, 30-39, 90-97 |
| 39 | default fg | removed color | 0, 30-39, 90-97 |
| 40 | background black | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 41 | bg red | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 42 | bg green | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 43 | bg yellow | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 44 | bg blue | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 45 | bg magenta | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 46 | bg cyan | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 47 | bg white | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 48 | bg custom | set background-color; see below for details | 0, 40-49, 100-107 |
| 49 | default bg | removed background-color | 0, 40-49, 100-107 |
| 90 | fg bright black | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 91 | fg bright red | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 92 | fg bright green | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 93 | fg bright yellow | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 94 | fg bright blue | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 95 | fg bright magenta | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 96 | fg bright cyan | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 97 | fg bright white | color: (value from options or defaultPalette) | 0, 30-39, 90-97 |
| 100 | bg bright black | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 101 | bg bright red | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 102 | bg bright green | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 103 | bg bright yellow | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 104 | bg bright blue | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 105 | bg bright magenta | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 106 | bg bright cyan | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
| 107 | bg bright white | background-color: (value from options or defaultPalette) | 0, 40-49, 100-107 |
Parameters 38 and 48 must be followed by 5;n or 2;r;g;b, where n, r, g & b are integers
between 0 and 255 (inclusive). Any missing or invalid parameters cause the 38/48 and all following
parameters to be ignored, accompanied by a warning.
The format 2;r;b;g cause color or background-color to be set to rgb(r,g,b).
The format 5;n has different meanings depending on the value of n:
- values 0 through 7 have the same meaning as "set standard color" (parameters 30-37/40-47);
- values 8 through 15 have the same meaning as "set bright color" (parameters 90-97/100-107);
- values 16 though 231 select a colors from a 6x6x6 colors cube,
computed using this formula:
16 + 36r + 6g + b; 0 ≤ r, g, b ≤ 5; - values 232 though 255 select a gray-scale color from #000000 to #fdfdfd,
computed using this formula:
232 + v * 11; 0 ≤ v ≤ 23.
Any non-SGR CSI sequence
is passed as-is. SGR parameter list must match /[0-9;]*/. Empty parameters, including an empty parameter list
is treated the same as "reset": \x1B[m is the same as \x1B[0m; \x1B[2;;1m is the same as \x1B[2;0;1m.
SGR parameters are processed in order they appear. \x1B[1;0m is semantically the same as
\x1B[0m, as the "bold" is immediately overwritten by a "reset". However, \x1B[0;1m is different,
as it first "resets" the style, then enables "bold". \x1B[1m is also different, as it only adds
"bold" to the previous style.
sgrp always escapes HTML in its input. This the default behavior when appending text to the DOM. Not escaping HTML would create malformed output if HTML is intermixed with ANSI escape codes.
Colors represents a set of css colors to use when converting ANSI SGR escape sequences to HTML span elements.
interface Colors {
black: string;
red: string;
green: string;
yellow: string;
blue: string;
magenta: string;
cyan: string;
white: string;
}Palette determines the css colors to use when converting ANSI SGR escape sequence to HTML span elements.
The "standard" colors are used by SGR parameters 30 to 37 and 40 to 47, while "bright" colors are used by SGR parameters 90 to 97 and 100 to 107.
interface Palette {
standard: Colors;
bright: Colors;
}PartialPalette is a variant of Palette with all attributes optional.
interface PartialPalette {
standard?: Partial<Colors>;
bright?: Partial<Colors>;
}Options customize the ANSI SGR to HTML span conversion process.
palette overrides colors from defaultPalette.
escapeControlCodes, if set to true, will cause control codes \x00-\x07 and \x0E-\x1F
to be replaced by corresponding control pictures
(U+2400-U+241F).
interface Options {
palette?: PartialPalette;
escapeControlCodes?: boolean;
}defaultPalette is the set of default colors used by sgrp.
This object is recursively frozen.
const defaultPalette: Palette = {
standard: {
black: "#0c0c0c",
red: "#c50f1f",
green: "#13a10e",
yellow: "#c19c00",
blue: "#0037da",
magenta: "#881798",
cyan: "#3a96dd",
white: "#cccccc",
},
bright: {
black: "#767676",
red: "#e74856",
green: "#16c60c",
yellow: "#f9f1a5",
blue: "#3b78ff",
magenta: "#b4009e",
cyan: "#61d6d6",
white: "#f2f2f2",
},
}SGRToStringTransformer is a Transformer<string, string> converting ANSI SGR escape sequences
to appropriately-styled HTML span elements.
Any incoming HTML data is escaped, as otherwise the output might become malformed
(think what would happen on this input: <\x1B[1mb\x1B[m>).
class SGRToStringTransformer implements Transformer<string, string> {
constructor(options: Options = {});
}transform processes a chunk of input string. Part of the TransformStream's transformer API.
class SGRToStringTransformer {
transform(chunk: string, controller: TransformStreamDefaultController<string>): void;
}flush marks the end of stream. Part of the TransformStream's transformer API.
class SGRToStringTransformer {
flush(controller: TransformStreamDefaultController<string>): void;
}Returns a new TransformStream around this SGRToStringTransformer.
class SGRToStringTransformer {
toStream(): TransformStream<string, string>;
}SGRToElementSink is a UnderlyingSink<string, string> converting ANSI SGR escape sequences
to appropriately-styled HTML span elements, which are incrementally appended to the provided
parent element.
Any incoming HTML data is escaped, as otherwise the output might become malformed
(think what would happen on this input: <\x1B[1mb\x1B[m>).
class SGRToElementSink implements UnderlyingSink<string> {
constructor(public element: Node, options: Options = {});
}write processes a chunk of input string. Part of the WritableStream's underlyingSink API.
class SGRToElementSink {
write(chunk: string, _controller?: WritableStreamDefaultController): void;
}close marks the end of stream. Part of the WritableStream's underlyingSink API.
class SGRToElementSink {
close(): void;
}Returns a new WritableStream around this SGRToElementSink.
class SGRToElementSink {
toStream(): WritableStream<string>;
}sgrToString converts text containing ANSI SGR escape sequences to text containing appropriately-styled HTML span elements.
Note that usage of this function should be reserved for testing purposes only. When appending data to the document, use sgrToElement. When writing data to a file or over a network, use SGRToStringTransformer directly to fully utilize JavaScript's streaming API.
Any HTML in the input is escaped, as otherwise the output might become malformed
(think what would happen on this input: <\x1B[1mb\x1B[m>).
function sgrToString(
source: string | ReadableStream<string>,
options: Options = {},
): Promise<string>sgrToElement converts text containing ANSI SGR escape sequences to a series of appropriately-styled HTML span elements and incrementally appends them to the provided DOM node.
function sgrToElement(
source: string | ReadableStream<string>,
element: Node,
options: Options = {},
): Promise<void>StringChunkSource implements UnderlyingDefaultSource<string> over a constant string value.
This makes it possible to jump-start a ReadableStream<string> using a single string.
class StringChunkSource implements UnderlyingDefaultSource<string> {
text: string;
offset: number = 0;
constructor(text: string);
pull(controller: ReadableStreamDefaultController<string>): void;
toStream(): ReadableStream<string>;
}StringChunkSink implements UnderlyingSink<string> by collecting all incoming strings
into an array. This makes it possible to jump-start WritableString<string> by collecting
all strings into memory.
class StringChunkSink implements UnderlyingSink<string> {
chunks: string[] = [];
constructor();
write(chunk: string, _controller?: WritableStreamDefaultController);
toStream(): WritableStream<string>;
toString(): string;
}sgrp is provided under the MIT license, included in the LICENSE file.