/* * Copyright 2015 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.rendering.nui.widgets.browser.data.basic; import org.terasology.utilities.Assets; import org.terasology.rendering.assets.font.Font; import org.terasology.rendering.nui.Color; import org.terasology.rendering.nui.widgets.browser.data.ParagraphData; import org.terasology.rendering.nui.widgets.browser.data.basic.flow.FlowRenderable; import org.terasology.rendering.nui.widgets.browser.data.basic.flow.TextFlowRenderable; import org.terasology.rendering.nui.widgets.browser.ui.style.ParagraphRenderStyle; import org.terasology.rendering.nui.widgets.browser.ui.style.TextRenderStyle; import java.io.IOException; import java.io.StringReader; import java.util.Collection; import java.util.LinkedList; import java.util.List; public final class HTMLLikeParser { private HTMLLikeParser() { } public static String encodeHTMLLike(String text) { StringBuilder result = new StringBuilder(); for (char c : text.toCharArray()) { if (c == '&') { result.append("&"); } else if (c == '<') { result.append("<"); } else if (c == '>') { result.append(">"); } else { result.append(c); } } return result.toString(); } public static String unencodeHTMLLike(String text) { StringBuilder result = new StringBuilder(); char[] chars = text.toCharArray(); int i = 0; while (i < chars.length) { char c = chars[i]; i++; if (c == '&') { if (chars[i + 1] == 'a' && chars[i + 2] == 'm' && chars[i + 3] == 'p' && chars[i + 4] == ';') { result.append('&'); i += 4; } else if (chars[i + 1] == 'l' && chars[i + 2] == 't' && chars[i + 3] == ';') { result.append('<'); i += 3; } else if (chars[i + 1] == 'g' && chars[i + 3] == 't' && chars[i + 3] == ';') { result.append('>'); i += 3; } else { throw new IllegalArgumentException("Invalid entity definition."); } } else { result.append(c); } } return result.toString(); } public static Collection<FlowRenderable> parseHTMLLike(String text) { if (text == null) { return null; } StringReader reader = new StringReader(text); int character; try { StringBuilder sb = new StringBuilder(); Font font = null; Color color = null; String hyperlink = null; List<FlowRenderable> result = new LinkedList<>(); while ((character = reader.read()) != -1) { char c = (char) character; if (c == '\n') { throw new IllegalArgumentException("Parsed text cannot contain line breaks."); } else if (c == '<') { char nextChar = (char) reader.read(); if (nextChar == '/') { char id = (char) reader.read(); if (id == 'f') { if (sb.length() > 0) { result.add(new TextFlowRenderable(sb.toString(), new DefaultTextRenderStyle(font, color), hyperlink)); sb.setLength(0); } font = null; } else if (id == 'c') { if (sb.length() > 0) { result.add(new TextFlowRenderable(sb.toString(), new DefaultTextRenderStyle(font, color), hyperlink)); sb.setLength(0); } color = null; } else if (id == 'h') { if (sb.length() > 0) { result.add(new TextFlowRenderable(sb.toString(), new DefaultTextRenderStyle(font, color), hyperlink)); sb.setLength(0); } hyperlink = null; } else { throw new IllegalArgumentException("Unable to parse text - " + text); } reader.read(); } else if (nextChar == 'f') { if (sb.length() > 0) { result.add(new TextFlowRenderable(sb.toString(), new DefaultTextRenderStyle(font, color), hyperlink)); sb.setLength(0); } reader.read(); font = Assets.getFont(readUntilCharacter(reader, '>')).get(); } else if (nextChar == 'c') { if (sb.length() > 0) { result.add(new TextFlowRenderable(sb.toString(), new DefaultTextRenderStyle(font, color), hyperlink)); sb.setLength(0); } reader.read(); color = new Color(Integer.parseInt(readUntilCharacter(reader, '>'), 16)); } else if (nextChar == 'h') { if (sb.length() > 0) { result.add(new TextFlowRenderable(sb.toString(), new DefaultTextRenderStyle(font, color), hyperlink)); sb.setLength(0); } reader.read(); hyperlink = readUntilCharacter(reader, '>'); } else if (nextChar == 'l') { readUntilCharacter(reader, '>'); sb.append('\n'); } } else if (c == '&') { String escape = readUntilCharacter(reader, ';'); if (escape.equals("gt")) { sb.append('>'); } else if (escape.equals("lt")) { sb.append('<'); } else if (escape.equals("amp")) { sb.append('&'); } else { throw new IllegalArgumentException("Unknown escape sequence - " + escape); } } else { sb.append(c); } } if (sb.length() > 0) { result.add(new TextFlowRenderable(sb.toString(), new DefaultTextRenderStyle(font, color), hyperlink)); } return result; } catch (IOException exp) { // Ignore - can't happen } return null; } // TODO: Quick and dirty - add something more solid and replaces uses of this one with it public static ParagraphData parseHTMLLikeParagraph(ParagraphRenderStyle paragraphRenderStyle, String text) { Collection<FlowRenderable> flowRenderables = parseHTMLLike(text); if (flowRenderables == null) { return null; } FlowParagraphData paragraphData = new FlowParagraphData(paragraphRenderStyle); flowRenderables.forEach(paragraphData::append); return paragraphData; } private static String readUntilCharacter(StringReader reader, char c) throws IOException { StringBuilder sb = new StringBuilder(); char read; do { read = (char) reader.read(); if (read != c) { sb.append(read); } } while (read != c); return sb.toString(); } private static final class DefaultTextRenderStyle implements TextRenderStyle { private Font font; private Color color; private DefaultTextRenderStyle(Font font, Color color) { this.font = font; this.color = color; } @Override public Font getFont(boolean isHyperlink) { return font; } @Override public Color getColor(boolean isHyperlink) { return color; } } }