Skip to content

Instantly share code, notes, and snippets.

@ajmas ajmas/vt100-to-html.js
Last active Jan 5, 2018

Embed
What would you like to do?
Quick attempt to convert ansi/vt100 escape sequence to HTML
let text = "\u001b[H\u001b(B\u001b[mtop - 02:11:23 up 36 days, 18:31, 0 users, load average: 4.25, 5.34, 5.98\u001b(B\u001b[m\u001b[39;49m\u001b(B\u001b[m\u001b[39;49m\u001b[K\r\n\r\n%Cpu(s):\u001b(B\u001b[m\u001b[39;49m\u001b[1m 21.7 \u001b(B\u001b[m\u001b[39;49;35;43mus,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 8.7 \u001b(B\u001b[m\u001b[39;49msy,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 0.0 \u001b(B\u001b[m\u001b[39;49mni,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 68.3 \u001b(B\u001b[m\u001b[39;49mid,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 0.0 \u001b(B\u001b[m\u001b[39;49mwa,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 0.0 \u001b(B\u001b[m\u001b[39;49mhi,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 1.1 \u001b(B\u001b[m\u001b[39;49msi,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 0.3 \u001b(B\u001b[m\u001b[39;49mst\u001b(B\u001b[m\u001b[39;49m\u001b(B\u001b[m\u001b[39;49m\u001b[K\r\nKiB Mem :\u001b(B\u001b[m\u001b[39;49m\u001b[1m 62916344 \u001b(B\u001b[m\u001b[39;49mtotal,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 3671244 \u001b(B\u001b[m\u001b[39;49mfree,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 39924484 \u001b(B\u001b[m\u001b[39;49mused,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 19320616 \u001b(B\u001b[m\u001b[39;49mbuff/cache\u001b(B\u001b[m\u001b[39;49m\u001b(B\u001b[m\u001b[39;49m\u001b[K\r\nKiB Swap:\u001b(B\u001b[m\u001b[39;49m\u001b[1m 63963132 \u001b(B\u001b[m\u001b[39;49mtotal,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 61726208 \u001b(B\u001b[m\u001b[39;49mfree,\u001b(B\u001b[m\u001b[39;49m\u001b[1m 2236924 \u001b(B\u001b[m\u001b[39;49mused.\u001b(B\u001b[m\u001b[39;49m\u001b[1m 22105164 \u001b(B\u001b[m\u001b[39;49mavail Mem \u001b(B\u001b[m\u001b[39;49m\u001b(B\u001b[m\u001b[39;49m\u001b[K\r\n\u001b[K\r\n\r\n\r\n\r\n\u001b[J\r\n\r\n\u001b[4mHello";
const escSeq = {
"7": null,
"8": null,
"[20h": null,
"[?1h": null,
"[?3h": null,
"[?4h": null,
"[?5h": null,
"[?6h": null,
"[?7h": null,
"[?8h": null,
"[?9h": null,
"[20l": null,
"[?1l": null,
"[?2l": null,
"[?3l": null,
"[?4l": null,
"[?5l": null,
"[?6l": null,
"[?7l": null,
"[?8l": null,
"[?9l": null,
"=": null,
">": null,
"(A": null,
")A": null,
"(B": null,
")B": null,
"(0": null,
")0": null,
"(1": null,
")1": null,
"(2": null,
")2": null,
"N": null,
"O": null,
// "[m": function (state) { if (state.spanCount > 0) {state.output +=
// '</span>'; state.spanCount--;} }, "[0m": function (state) { if
// (state.spanCount > 0) {state.output += '</span>'; state.spanCount--;} },
// "[1m": { 'class': 'bold' }, "[2m": { 'class': 'light' }, "[4m": {
// 'class': 'underline' }, "[5m": { 'class': 'blink' }, "[7m": { 'class':
// 'reverse' }, "[8m": { 'class': 'invisible' },
"[;r": null,
"[A": null,
"[B": null,
"[C": null,
"[D": null,
"[H": null,
"[;H": null,
"[f": null,
"[;f": null,
"D": null,
"M": null,
"E": null,
"H": null,
"[g": null,
"[0g": null,
"[3g": null,
"#3": null,
"#4": null,
"#5": null,
"#6": null,
"[K": null,
"[0K": null,
"[1K": null,
"[2K": null,
"[J": null,
"[0J": null,
"[1J": null,
"[2J": null,
"5n": null,
"0n": null,
"3n": null,
"6n": null,
";R": null,
"[c": null,
"[0c": null,
"[?1;0c": null,
"c": null,
"#8": null,
"[2;1y": null,
"[2;2y": null,
"[2;9y": null,
"[2;10y": null,
"[0q": null,
"[1q": null,
"[2q": null,
"[3q": null,
"[4q": null
}
const modeClasses = {
'1': 'bold',
'2': 'light',
'3': 'underline',
'4': 'blink',
'5': 'reverse',
'6': 'invisible'
}
const modeStyles = {
'30': 'color: black',
'31': 'color: red',
'32': 'color: green',
'33': 'color: yellow',
'34': 'color: blue',
'35': 'color: magenta',
'36': 'color: cyan',
'37': 'color: white',
'40': 'background-color: black',
'41': 'background-color: red',
'42': 'background-color: green',
'43': 'background-color: yellow',
'44': 'background-color: blue',
'45': 'background-color: magenta',
'46': 'background-color: cyan',
'47': 'background-color: white'
}
function processModes(escapeTxt, state) {
var modes = escapeTxt.substring(1, escapeTxt.length - 1);
if (modes.length > 0) {
modes = modes.split(';');
for (let i = 0; i < modes.length; i++) {
if (modeClasses[modes[i]]) {
state
.classes
.push(modeClasses[modes[i]]);
} else if (modeStyles[modes[i]]) {
state
.styles
.push(modeStyles[modes[i]]);
}
}
} else {
if (state.spanCount > 0) {
state.output += '</span>';
state.spanCount--;
}
}
}
function isLetter(str) {
return str.length === 1 && str.match(/[a-z]/i);
}
function isDigit(str) {
return str.length === 1 && str.match(/[0-9]/i);
}
function processEscape(escapeTxt, state) {
if (escapeTxt.startsWith('[') && escapeTxt.endsWith('m')) {
processModes(escapeTxt, state);
} else {
const entry = escSeq[escapeTxt];
if (entry && entry !== null) {
if (typeof entry === 'object') {
if (entry.class) {
state
.classes
.push(entry.class);
}
if (entry.style) {
state
.styles
.push(entry.stye);
}
} else if (typeof entry === 'function') {
entry(state);
}
}
}
}
var escapeTxt = '';
var state = {
output: '',
spanCount: 0,
classes: [],
styles: []
}
for (let i = 0; i < text.length; i++) {
let character = text.charAt(i);
if (character === '\u001b') {
escapeTxt = text.charAt(++i);
if (escapeTxt === '[') {
// process until character
do {
character = text.charAt(++i)
escapeTxt += character;
} while (!isLetter(character) && i < text.length);
} else if (escapeTxt === '#') {
// process until digit
do {
character = text.charAt(++i)
escapeTxt += character;
} while (!isDigit(character) && i < text.length);
} else if (escapeTxt === '(' || escapeTxt === ')') {
// process another char
escapeTxt += text.charAt(++i);
} else {
// that's the escape
}
processEscape(escapeTxt, state);
} else {
if (state.classes.length > 0 || state.styles.length > 0) {
state.output += `<span class="${state
.classes
.join(' ')}" style="${state
.styles
.join(';')}">`;
state.classes = [];
state.styles = [];
state.spanCount++;
}
state.output += character;
}
}
if (state.spanCount > 0) {
for (let i = 0; i < state.spanCount; i++) {
state.output += '</span>';
}
}
const styleDefs = '@keyframes blinker { 50% { opacity: 0; } }\n.bold { font-weight: bold; } \n.unde' +
'rline { text-decoration: underline; } \n.blink { animation: blinker 2s linear in' +
'finite; } \n.invisible { display: none; } \n';
console.log(`<html><head><style type="text/css">${styleDefs}</style></head><body><pre>${state.output}</pre></body></html>`);
// https://www.csie.ntu.edu.tw/~r92094/c++/VT100.html
// http://www.termsys.demon.co.uk/vtansi.htm
// https://misc.flogisoft.com/bash/tip_colors_and_formatting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.