2019-01-07 22:09:20 +01:00
|
|
|
package net.ME1312.SubServers.Console.Library;
|
2019-01-06 21:58:15 +01:00
|
|
|
|
2020-06-12 07:45:49 +02:00
|
|
|
import net.ME1312.Galaxi.Library.Container.Container;
|
2020-11-16 21:34:59 +01:00
|
|
|
|
2019-01-06 21:58:15 +01:00
|
|
|
import org.fusesource.jansi.AnsiOutputStream;
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
import java.awt.*;
|
2019-01-06 21:58:15 +01:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.util.LinkedList;
|
2021-06-13 05:20:41 +02:00
|
|
|
import java.util.Locale;
|
|
|
|
|
|
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
2019-01-06 21:58:15 +01:00
|
|
|
|
|
|
|
public class HTMLogger extends AnsiOutputStream {
|
|
|
|
private static final String[] ANSI_COLOR_MAP = new String[]{"000000", "cd0000", "25bc24", "d7d700", "0000c3", "be00be", "00a5dc", "cccccc"};
|
|
|
|
private static final String[] ANSI_BRIGHT_COLOR_MAP = new String[]{"808080", "ff0000", "31e722", "ffff00", "0000ff", "ff00ff", "00c8ff", "ffffff"};
|
2021-06-13 05:20:41 +02:00
|
|
|
private static final byte[] BYTES_NBSP = "\u00A0".getBytes(UTF_8);
|
|
|
|
private static final byte[] BYTES_AMP = "&".getBytes(UTF_8);
|
|
|
|
private static final byte[] BYTES_LT = "<".getBytes(UTF_8);
|
|
|
|
private LinkedList<String> currentAttributes = new LinkedList<String>();
|
|
|
|
private LinkedList<String> queue = new LinkedList<String>();
|
2019-01-06 21:58:15 +01:00
|
|
|
private OutputStream raw;
|
|
|
|
protected boolean ansi = true;
|
2021-06-13 05:20:41 +02:00
|
|
|
protected boolean nbsp = false;
|
2019-01-06 21:58:15 +01:00
|
|
|
private boolean underline = false;
|
|
|
|
private boolean strikethrough = false;
|
|
|
|
|
|
|
|
public static HTMLogger wrap(OutputStream raw) {
|
|
|
|
return wrap(raw, new HTMConstructor<HTMLogger>() {
|
|
|
|
@Override
|
2021-06-13 05:20:41 +02:00
|
|
|
public HTMLogger construct(OutputStream raw, OutputStream wrapped) {
|
|
|
|
return new HTMLogger(raw, wrapped);
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public static <T extends HTMLogger> T wrap(final OutputStream raw, HTMConstructor<T> constructor) {
|
2021-06-13 05:20:41 +02:00
|
|
|
final Container<T> html = new Container<T>(null);
|
2020-11-14 08:07:25 +01:00
|
|
|
html.value(constructor.construct(raw, new OutputStream() {
|
2019-01-06 21:58:15 +01:00
|
|
|
private boolean nbsp = false;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void write(int data) throws IOException {
|
2021-06-13 05:20:41 +02:00
|
|
|
HTMLogger htm = html.value();
|
|
|
|
if (htm.queue.size() > 0) {
|
|
|
|
LinkedList<String> queue = htm.queue;
|
|
|
|
htm.queue = new LinkedList<>();
|
|
|
|
for (String attr : queue) {
|
|
|
|
htm.write('<' + attr + '>');
|
|
|
|
htm.currentAttributes.addFirst(attr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-06 21:58:15 +01:00
|
|
|
if (data == 32) {
|
2021-06-13 05:20:41 +02:00
|
|
|
if (htm.nbsp) {
|
|
|
|
if (nbsp) raw.write(BYTES_NBSP);
|
|
|
|
else raw.write(data);
|
|
|
|
nbsp = !nbsp;
|
|
|
|
} else raw.write(data);
|
2019-01-06 21:58:15 +01:00
|
|
|
} else {
|
|
|
|
nbsp = false;
|
|
|
|
switch(data) {
|
2021-06-13 05:20:41 +02:00
|
|
|
case '&':
|
2019-01-06 21:58:15 +01:00
|
|
|
raw.write(BYTES_AMP);
|
|
|
|
break;
|
2021-06-13 05:20:41 +02:00
|
|
|
case '<':
|
2019-01-06 21:58:15 +01:00
|
|
|
raw.write(BYTES_LT);
|
|
|
|
break;
|
2021-06-13 05:20:41 +02:00
|
|
|
case '\n':
|
|
|
|
htm.closeAttributes();
|
2019-01-06 21:58:15 +01:00
|
|
|
default:
|
|
|
|
raw.write(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
2020-11-14 08:07:25 +01:00
|
|
|
return html.value();
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
2021-06-13 05:20:41 +02:00
|
|
|
public HTMLogger(final OutputStream raw, OutputStream wrapped) {
|
2019-01-06 21:58:15 +01:00
|
|
|
super(wrapped);
|
|
|
|
this.raw = raw;
|
|
|
|
}
|
|
|
|
public interface HTMConstructor<T extends HTMLogger> {
|
|
|
|
T construct(OutputStream raw, OutputStream wrapped);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void write(String s) throws IOException {
|
2021-06-13 05:20:41 +02:00
|
|
|
raw.write(s.getBytes(UTF_8));
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
private void writeAttribute(String attr) throws IOException {
|
|
|
|
queue.add(attr);
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
public void closeAttribute(String s) throws IOException {
|
|
|
|
|
|
|
|
// Try to remove a tag that doesn't exist yet first
|
|
|
|
String[] queue = this.queue.toArray(new String[0]);
|
|
|
|
for (int i = queue.length; i > 0;) {
|
|
|
|
String attr = queue[--i];
|
|
|
|
if (attr.toLowerCase().startsWith(s.toLowerCase())) {
|
|
|
|
this.queue.removeLastOccurrence(attr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close a tag that we've already written
|
2019-01-06 21:58:15 +01:00
|
|
|
LinkedList<String> closedAttributes = new LinkedList<String>();
|
2021-06-13 05:20:41 +02:00
|
|
|
LinkedList<String> currentAttributes = new LinkedList<String>(this.currentAttributes);
|
2019-01-06 21:58:15 +01:00
|
|
|
LinkedList<String> unclosedAttributes = new LinkedList<String>();
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
for (String attr : currentAttributes) {
|
2019-01-06 21:58:15 +01:00
|
|
|
if (attr.toLowerCase().startsWith(s.toLowerCase())) {
|
|
|
|
for (String a : unclosedAttributes) {
|
2021-06-13 05:20:41 +02:00
|
|
|
closedAttributes.add(a);
|
|
|
|
this.currentAttributes.removeFirst();
|
|
|
|
write("</" + a.split(" ", 2)[0] + '>');
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
unclosedAttributes.clear();
|
2021-06-13 05:20:41 +02:00
|
|
|
this.currentAttributes.removeFirst();
|
|
|
|
write("</" + attr.split(" ", 2)[0] + '>');
|
|
|
|
break;
|
2019-01-06 21:58:15 +01:00
|
|
|
} else {
|
|
|
|
unclosedAttributes.add(attr);
|
|
|
|
}
|
|
|
|
}
|
2021-06-13 05:20:41 +02:00
|
|
|
|
|
|
|
// Queue unrelated tags to be re-opened
|
2019-01-06 21:58:15 +01:00
|
|
|
for (String attr : closedAttributes) {
|
2021-06-13 05:20:41 +02:00
|
|
|
this.queue.addFirst(attr);
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
public void closeAttributes() throws IOException {
|
|
|
|
queue.clear();
|
|
|
|
|
|
|
|
for (String attr : currentAttributes) {
|
2019-01-06 21:58:15 +01:00
|
|
|
write("</" + attr.split(" ", 2)[0] + ">");
|
|
|
|
}
|
|
|
|
|
|
|
|
underline = false;
|
|
|
|
strikethrough = false;
|
2021-06-13 05:20:41 +02:00
|
|
|
currentAttributes.clear();
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processDeleteLine(int amount) throws IOException {
|
|
|
|
super.processDeleteLine(amount);
|
|
|
|
}
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
private void renderTextDecoration() throws IOException {
|
2019-01-06 21:58:15 +01:00
|
|
|
String dec = "";
|
|
|
|
if (underline) dec += " underline";
|
|
|
|
if (strikethrough) dec += " line-through";
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
closeAttribute("span style=\"text-decoration:");
|
|
|
|
if (dec.length() != 0) writeAttribute("span style=\"text-decoration:" + dec.substring(1) + "\"");
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetAttribute(int attribute) throws IOException {
|
|
|
|
if (ansi) switch(attribute) {
|
|
|
|
case 1:
|
|
|
|
closeAttribute("b");
|
|
|
|
writeAttribute("b");
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
closeAttribute("i");
|
|
|
|
writeAttribute("i");
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
underline = true;
|
2021-06-13 05:20:41 +02:00
|
|
|
renderTextDecoration();
|
2019-01-06 21:58:15 +01:00
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
strikethrough = true;
|
2021-06-13 05:20:41 +02:00
|
|
|
renderTextDecoration();
|
2019-01-06 21:58:15 +01:00
|
|
|
break;
|
|
|
|
case 22:
|
|
|
|
closeAttribute("b");
|
|
|
|
break;
|
|
|
|
case 23:
|
|
|
|
closeAttribute("i");
|
|
|
|
break;
|
|
|
|
case 24:
|
|
|
|
underline = false;
|
2021-06-13 05:20:41 +02:00
|
|
|
renderTextDecoration();
|
2019-01-06 21:58:15 +01:00
|
|
|
break;
|
|
|
|
case 29:
|
|
|
|
strikethrough = false;
|
2021-06-13 05:20:41 +02:00
|
|
|
renderTextDecoration();
|
|
|
|
break;
|
|
|
|
case 73:
|
|
|
|
closeAttribute("su");
|
|
|
|
writeAttribute("sup");
|
|
|
|
break;
|
|
|
|
case 74:
|
|
|
|
closeAttribute("su");
|
|
|
|
writeAttribute("sub");
|
|
|
|
break;
|
|
|
|
case 75:
|
|
|
|
closeAttribute("su");
|
2019-01-06 21:58:15 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processUnknownOperatingSystemCommand(int label, String arg) {
|
|
|
|
try {
|
2021-06-13 05:20:41 +02:00
|
|
|
if (ansi && label == 8) {
|
|
|
|
closeAttribute("a");
|
|
|
|
String[] args = arg.split(";", 3);
|
|
|
|
if (args.length > 1 && args[1].length() > 0 && allowHyperlink(args[1])) {
|
|
|
|
writeAttribute("a href=\"" + args[1].replace("&", "&").replace("<", "<").replace("\"", """) + "\" target=\"_blank\"");
|
|
|
|
}
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
} catch (Exception e) {}
|
|
|
|
}
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
protected boolean allowHyperlink(String link) {
|
|
|
|
if (link.toLowerCase(Locale.ENGLISH).startsWith("mailto:execute@galaxi.engine")) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-06 21:58:15 +01:00
|
|
|
@Override
|
|
|
|
protected void processAttributeRest() throws IOException {
|
|
|
|
closeAttributes();
|
|
|
|
}
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
private String parse256(int color) throws IOException {
|
2019-01-06 21:58:15 +01:00
|
|
|
if (color < 8) {
|
|
|
|
return ANSI_COLOR_MAP[color];
|
|
|
|
} else if (color < 16) {
|
|
|
|
return ANSI_BRIGHT_COLOR_MAP[color - 8];
|
|
|
|
} else if (color < 232) {
|
|
|
|
int r = (int) (Math.floor((color - 16) / 36d) * (255 / 5));
|
|
|
|
int g = (int) (Math.floor(((color - 16) % 36d) / 6d) * (255 / 5));
|
|
|
|
int b = (int) (Math.floor(((color - 16) % 36d) % 6d) * (255 / 5));
|
|
|
|
return ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16);
|
|
|
|
} else if (color < 256) {
|
|
|
|
int gray = (int) ((255 / 25d) * (color - 232 + 1));
|
|
|
|
return ((gray >= 16)?"":"0") + Integer.toString(gray, 16) + ((gray >= 16)?"":"0") + Integer.toString(gray, 16) + ((gray >= 16)?"":"0") + Integer.toString(gray, 16);
|
|
|
|
} else {
|
|
|
|
throw new IOException("Invalid 8 Bit Color: " + color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processDefaultTextColor() throws IOException {
|
2021-06-13 05:20:41 +02:00
|
|
|
closeAttribute("span style=\"color:");
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetForegroundColor(int color) throws IOException {
|
|
|
|
processSetForegroundColor(color, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetForegroundColor(int color, boolean bright) throws IOException {
|
|
|
|
if (ansi) {
|
|
|
|
processDefaultTextColor();
|
2021-06-13 05:20:41 +02:00
|
|
|
writeAttribute("span style=\"color:#" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + "\"");
|
|
|
|
renderTextDecoration();
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetForegroundColorExt(int index) throws IOException {
|
|
|
|
if (ansi) {
|
|
|
|
processDefaultTextColor();
|
2021-06-13 05:20:41 +02:00
|
|
|
writeAttribute("span style=\"color:#" + parse256(index) + "\"");
|
|
|
|
renderTextDecoration();
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetForegroundColorExt(int r, int g, int b) throws IOException {
|
|
|
|
if (ansi) {
|
|
|
|
processDefaultTextColor();
|
2021-06-13 05:20:41 +02:00
|
|
|
writeAttribute("span style=\"color:#" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + "\"");
|
|
|
|
renderTextDecoration();
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processDefaultBackgroundColor() throws IOException {
|
2021-06-13 05:20:41 +02:00
|
|
|
closeAttribute("span style=\"background-color:");
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetBackgroundColor(int color) throws IOException {
|
|
|
|
processSetBackgroundColor(color, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
|
|
|
|
if (ansi) {
|
|
|
|
processDefaultBackgroundColor();
|
2021-06-13 05:20:41 +02:00
|
|
|
writeAttribute("span style=\"background-color:#" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + "\"");
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetBackgroundColorExt(int index) throws IOException {
|
|
|
|
if (ansi) {
|
|
|
|
processDefaultBackgroundColor();
|
2021-06-13 05:20:41 +02:00
|
|
|
writeAttribute("span style=\"background-color:#" + parse256(index) + "\"");
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException {
|
|
|
|
if (ansi) {
|
|
|
|
processDefaultBackgroundColor();
|
2021-06-13 05:20:41 +02:00
|
|
|
writeAttribute("span style=\"background-color:#" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + "\"");
|
2019-01-06 21:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-13 05:20:41 +02:00
|
|
|
@Override
|
|
|
|
public void flush() throws IOException {
|
|
|
|
super.flush();
|
|
|
|
raw.flush();
|
|
|
|
}
|
|
|
|
|
2019-01-06 21:58:15 +01:00
|
|
|
@Override
|
|
|
|
public void close() throws IOException {
|
|
|
|
closeAttributes();
|
|
|
|
super.close();
|
|
|
|
raw.close();
|
|
|
|
}
|
|
|
|
}
|