| /******************************************************************************* |
| * Copyright (c) 2012-2013 Laurent CARON |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Laurent CARON (laurent.caron at gmail dot com) - Initial implementation and API |
| *******************************************************************************/ |
| package org.mihalis.opal.utils; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyleRange; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontData; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| |
| /** |
| * Instances of this class are used to convert pseudo-HTML content of a styled |
| * text into style ranges. |
| */ |
| public class HTMLStyledTextParser { |
| |
| /** The styled text. */ |
| private final StyledText styledText; |
| |
| /** The output. */ |
| private StringBuilder output; |
| |
| /** The current tag. */ |
| private StringBuilder currentTag; |
| |
| /** The list of styles. */ |
| private final List<StyleRange> listOfStyles; |
| |
| /** The stack. */ |
| private final LinkedList<StyleRange> stack; |
| |
| /** The current position. */ |
| private int currentPosition; |
| |
| /** The default height. */ |
| private final int defaultHeight; |
| |
| /** The Constant HTML_CODES. */ |
| private static final Map<String, Integer[]> HTML_CODES = initHTMLCode(); |
| |
| /** |
| * Constructor. |
| * |
| * @param styledText styled text to analyze |
| */ |
| HTMLStyledTextParser(final StyledText styledText) { |
| this.styledText = styledText; |
| listOfStyles = new ArrayList<StyleRange>(); |
| stack = new LinkedList<StyleRange>(); |
| final FontData data = styledText.getFont().getFontData()[0]; |
| defaultHeight = data.getHeight(); |
| } |
| |
| /** |
| * Inits the html code. |
| * |
| * @return the map |
| */ |
| private static Map<String, Integer[]> initHTMLCode() { |
| final Map<String, Integer[]> map = new HashMap<String, Integer[]>(); |
| |
| map.put("aliceblue", new Integer[] { 240, 248, 255 }); |
| map.put("antiquewhite", new Integer[] { 250, 235, 215 }); |
| map.put("aqua", new Integer[] { 0, 255, 255 }); |
| map.put("aquamarine", new Integer[] { 127, 255, 212 }); |
| map.put("azure", new Integer[] { 240, 255, 255 }); |
| map.put("beige", new Integer[] { 245, 245, 220 }); |
| map.put("bisque", new Integer[] { 255, 228, 196 }); |
| map.put("black", new Integer[] { 0, 0, 0 }); |
| map.put("blanchedalmond", new Integer[] { 255, 235, 205 }); |
| map.put("blue", new Integer[] { 0, 0, 255 }); |
| map.put("blueviolet", new Integer[] { 138, 43, 226 }); |
| map.put("brown", new Integer[] { 165, 42, 42 }); |
| map.put("burlywood", new Integer[] { 222, 184, 135 }); |
| map.put("cadetblue", new Integer[] { 95, 158, 160 }); |
| map.put("chartreuse", new Integer[] { 127, 255, 0 }); |
| map.put("chocolate", new Integer[] { 210, 105, 30 }); |
| map.put("coral", new Integer[] { 255, 127, 80 }); |
| map.put("cornflowerblue", new Integer[] { 100, 149, 237 }); |
| map.put("cornsilk", new Integer[] { 255, 248, 220 }); |
| map.put("crimson", new Integer[] { 220, 20, 60 }); |
| map.put("cyan", new Integer[] { 0, 255, 255 }); |
| map.put("darkblue", new Integer[] { 0, 0, 139 }); |
| map.put("darkcyan", new Integer[] { 0, 139, 139 }); |
| map.put("darkgoldenrod", new Integer[] { 184, 134, 11 }); |
| map.put("darkgray", new Integer[] { 169, 169, 169 }); |
| map.put("darkgreen", new Integer[] { 0, 100, 0 }); |
| map.put("darkgrey", new Integer[] { 169, 169, 169 }); |
| map.put("darkkhaki", new Integer[] { 189, 183, 107 }); |
| map.put("darkmagenta", new Integer[] { 139, 0, 139 }); |
| map.put("darkolivegreen", new Integer[] { 85, 107, 47 }); |
| map.put("darkorange", new Integer[] { 255, 140, 0 }); |
| map.put("darkorchid", new Integer[] { 153, 50, 204 }); |
| map.put("darkred", new Integer[] { 139, 0, 0 }); |
| map.put("darksalmon", new Integer[] { 233, 150, 122 }); |
| map.put("darkseagreen", new Integer[] { 143, 188, 143 }); |
| map.put("darkslateblue", new Integer[] { 72, 61, 139 }); |
| map.put("darkslategray", new Integer[] { 47, 79, 79 }); |
| map.put("darkslategrey", new Integer[] { 47, 79, 79 }); |
| map.put("darkturquoise", new Integer[] { 0, 206, 209 }); |
| map.put("darkviolet", new Integer[] { 148, 0, 211 }); |
| map.put("deeppink", new Integer[] { 255, 20, 147 }); |
| map.put("deepskyblue", new Integer[] { 0, 191, 255 }); |
| map.put("dimgray", new Integer[] { 105, 105, 105 }); |
| map.put("dimgrey", new Integer[] { 105, 105, 105 }); |
| map.put("dodgerblue", new Integer[] { 30, 144, 255 }); |
| map.put("firebrick", new Integer[] { 178, 34, 34 }); |
| map.put("floralwhite", new Integer[] { 255, 250, 240 }); |
| map.put("forestgreen", new Integer[] { 34, 139, 34 }); |
| map.put("fuchsia", new Integer[] { 255, 0, 255 }); |
| map.put("gainsboro", new Integer[] { 220, 220, 220 }); |
| map.put("ghostwhite", new Integer[] { 248, 248, 255 }); |
| map.put("gold", new Integer[] { 255, 215, 0 }); |
| map.put("goldenrod", new Integer[] { 218, 165, 32 }); |
| map.put("gray", new Integer[] { 128, 128, 128 }); |
| map.put("green", new Integer[] { 0, 128, 0 }); |
| map.put("greenyellow", new Integer[] { 173, 255, 47 }); |
| map.put("grey", new Integer[] { 128, 128, 128 }); |
| map.put("honeydew", new Integer[] { 240, 255, 240 }); |
| map.put("hotpink", new Integer[] { 255, 105, 180 }); |
| map.put("indianred", new Integer[] { 205, 92, 92 }); |
| map.put("indigo", new Integer[] { 75, 0, 130 }); |
| map.put("ivory", new Integer[] { 255, 255, 240 }); |
| map.put("khaki", new Integer[] { 240, 230, 140 }); |
| map.put("lavender", new Integer[] { 230, 230, 250 }); |
| map.put("lavenderblush", new Integer[] { 255, 240, 245 }); |
| map.put("lawngreen", new Integer[] { 124, 252, 0 }); |
| map.put("lemonchiffon", new Integer[] { 255, 250, 205 }); |
| map.put("lightblue", new Integer[] { 173, 216, 230 }); |
| map.put("lightcoral", new Integer[] { 240, 128, 128 }); |
| map.put("lightcyan", new Integer[] { 224, 255, 255 }); |
| map.put("lightgoldenrodyellow", new Integer[] { 250, 250, 210 }); |
| map.put("lightgray", new Integer[] { 211, 211, 211 }); |
| map.put("lightgreen", new Integer[] { 144, 238, 144 }); |
| map.put("lightgrey", new Integer[] { 211, 211, 211 }); |
| map.put("lightpink", new Integer[] { 255, 182, 193 }); |
| map.put("lightsalmon", new Integer[] { 255, 160, 122 }); |
| map.put("lightseagreen", new Integer[] { 32, 178, 170 }); |
| map.put("lightskyblue", new Integer[] { 135, 206, 250 }); |
| map.put("lightslategray", new Integer[] { 119, 136, 153 }); |
| map.put("lightslategrey", new Integer[] { 119, 136, 153 }); |
| map.put("lightsteelblue", new Integer[] { 176, 196, 222 }); |
| map.put("lightyellow", new Integer[] { 255, 255, 224 }); |
| map.put("lime", new Integer[] { 0, 255, 0 }); |
| map.put("limegreen", new Integer[] { 50, 205, 50 }); |
| map.put("linen", new Integer[] { 250, 240, 230 }); |
| map.put("magenta", new Integer[] { 255, 0, 255 }); |
| map.put("maroon", new Integer[] { 128, 0, 0 }); |
| map.put("mediumaquamarine", new Integer[] { 102, 205, 170 }); |
| map.put("mediumblue", new Integer[] { 0, 0, 205 }); |
| map.put("mediumorchid", new Integer[] { 186, 85, 211 }); |
| map.put("mediumpurple", new Integer[] { 147, 112, 219 }); |
| map.put("mediumseagreen", new Integer[] { 60, 179, 113 }); |
| map.put("mediumslateblue", new Integer[] { 123, 104, 238 }); |
| map.put("mediumspringgreen", new Integer[] { 0, 250, 154 }); |
| map.put("mediumturquoise", new Integer[] { 72, 209, 204 }); |
| map.put("mediumvioletred", new Integer[] { 199, 21, 133 }); |
| map.put("midnightblue", new Integer[] { 25, 25, 112 }); |
| map.put("mintcream", new Integer[] { 245, 255, 250 }); |
| map.put("mistyrose", new Integer[] { 255, 228, 225 }); |
| map.put("moccasin", new Integer[] { 255, 228, 181 }); |
| map.put("navajowhite", new Integer[] { 255, 222, 173 }); |
| map.put("navy", new Integer[] { 0, 0, 128 }); |
| map.put("oldlace", new Integer[] { 253, 245, 230 }); |
| map.put("olive", new Integer[] { 128, 128, 0 }); |
| map.put("olivedrab", new Integer[] { 107, 142, 35 }); |
| map.put("orange", new Integer[] { 255, 165, 0 }); |
| map.put("orangered", new Integer[] { 255, 69, 0 }); |
| map.put("orchid", new Integer[] { 218, 112, 214 }); |
| map.put("palegoldenrod", new Integer[] { 238, 232, 170 }); |
| map.put("palegreen", new Integer[] { 152, 251, 152 }); |
| map.put("paleturquoise", new Integer[] { 175, 238, 238 }); |
| map.put("palevioletred", new Integer[] { 219, 112, 147 }); |
| map.put("papayawhip", new Integer[] { 255, 239, 213 }); |
| map.put("peachpuff", new Integer[] { 255, 218, 185 }); |
| map.put("peru", new Integer[] { 205, 133, 63 }); |
| map.put("pink", new Integer[] { 255, 192, 203 }); |
| map.put("plum", new Integer[] { 221, 160, 221 }); |
| map.put("powderblue", new Integer[] { 176, 224, 230 }); |
| map.put("purple", new Integer[] { 128, 0, 128 }); |
| map.put("red", new Integer[] { 255, 0, 0 }); |
| map.put("rosybrown", new Integer[] { 188, 143, 143 }); |
| map.put("royalblue", new Integer[] { 65, 105, 225 }); |
| map.put("saddlebrown", new Integer[] { 139, 69, 19 }); |
| map.put("salmon", new Integer[] { 250, 128, 114 }); |
| map.put("sandybrown", new Integer[] { 244, 164, 96 }); |
| map.put("seagreen", new Integer[] { 46, 139, 87 }); |
| map.put("seashell", new Integer[] { 255, 245, 238 }); |
| map.put("sienna", new Integer[] { 160, 82, 45 }); |
| map.put("silver", new Integer[] { 192, 192, 192 }); |
| map.put("skyblue", new Integer[] { 135, 206, 235 }); |
| map.put("slateblue", new Integer[] { 106, 90, 205 }); |
| map.put("slategray", new Integer[] { 112, 128, 144 }); |
| map.put("slategrey", new Integer[] { 112, 128, 144 }); |
| map.put("snow", new Integer[] { 255, 250, 250 }); |
| map.put("springgreen", new Integer[] { 0, 255, 127 }); |
| map.put("steelblue", new Integer[] { 70, 130, 180 }); |
| map.put("tan", new Integer[] { 210, 180, 140 }); |
| map.put("teal", new Integer[] { 0, 128, 128 }); |
| map.put("thistle", new Integer[] { 216, 191, 216 }); |
| map.put("tomato", new Integer[] { 255, 99, 71 }); |
| map.put("turquoise", new Integer[] { 64, 224, 208 }); |
| map.put("violet", new Integer[] { 238, 130, 238 }); |
| map.put("wheat", new Integer[] { 245, 222, 179 }); |
| map.put("white", new Integer[] { 255, 255, 255 }); |
| map.put("whitesmoke", new Integer[] { 245, 245, 245 }); |
| map.put("yellow", new Integer[] { 255, 255, 0 }); |
| map.put("yellowgreen", new Integer[] { 154, 205, 50 }); |
| |
| return map; |
| } |
| |
| /** |
| * Parse the content, build the list of style ranges and apply them to the |
| * styled text widget. |
| * |
| * @throws IOException Signals that an I/O exception has occurred. |
| */ |
| public void parse() throws IOException { |
| if (styledText == null || "".equals(styledText.getText().trim())) { |
| return; |
| } |
| |
| initBeforeParsing(); |
| |
| final String text = styledText.getText().trim(); |
| final int max = text.length(); |
| boolean inTag = false; |
| |
| for (int i = 0; i < max; i++) { |
| final char currentChar = text.charAt(i); |
| if (currentChar == '<') { |
| inTag = true; |
| continue; |
| } else if (currentChar == '>') { |
| inTag = false; |
| handleTag(); |
| currentTag.delete(0, currentTag.length()); |
| } else { |
| if (inTag) { |
| currentTag.append(currentChar); |
| } else { |
| currentPosition++; |
| output.append(currentChar); |
| } |
| } |
| } |
| styledText.setText(output.toString()); |
| styledText.setStyleRanges(listOfStyles.toArray(new StyleRange[listOfStyles.size()])); |
| } |
| |
| /** |
| * Inits the before parsing. |
| */ |
| private void initBeforeParsing() { |
| output = new StringBuilder(); |
| currentTag = new StringBuilder(); |
| listOfStyles.clear(); |
| stack.clear(); |
| currentPosition = 0; |
| } |
| |
| /** |
| * Handle tag. |
| */ |
| private void handleTag() { |
| final String tag = currentTag.toString().toLowerCase(); |
| if ("br".equals(tag) || "br/".equals(tag)) { |
| output.append("\n"); |
| currentPosition++; |
| return; |
| } |
| |
| if ("b".equals(tag)) { |
| processBeginBold(); |
| return; |
| } |
| if ("i".equals(tag)) { |
| processBeginItalic(); |
| return; |
| } |
| if ("u".equals(tag)) { |
| processBeginUnderline(); |
| return; |
| } |
| if (tag.startsWith("size=")) { |
| processBeginSize(); |
| return; |
| } |
| if (tag.startsWith("color=")) { |
| processBeginColor(); |
| return; |
| } |
| if (tag.startsWith("backgroundcolor=")) { |
| processBeginBackgroundColor(); |
| return; |
| } |
| |
| final String[] acceptedClosingTags = new String[] { "/b", "/i", "/u", "/size", "/color", "/backgroundcolor" }; |
| for (final String closingTag : acceptedClosingTags) { |
| if (closingTag.equals(tag)) { |
| processEndTag(closingTag); |
| return; |
| } |
| } |
| |
| final String text = "<" + tag + ">"; |
| output.append(text); |
| currentPosition += text.length(); |
| |
| } |
| |
| /** |
| * Process begin bold. |
| */ |
| private void processBeginBold() { |
| final StyleRange currentStyleRange = new StyleRange(); |
| currentStyleRange.start = currentPosition; |
| currentStyleRange.length = 0; |
| currentStyleRange.fontStyle = SWT.BOLD; |
| currentStyleRange.data = "</b>"; |
| stack.push(currentStyleRange); |
| } |
| |
| /** |
| * Process end tag. |
| * |
| * @param expectedTag the expected tag |
| */ |
| private void processEndTag(final String expectedTag) { |
| final StyleRange currentStyleRange = stack.pop(); |
| final String wholeExpectedTag = "<" + expectedTag + ">"; |
| if (!wholeExpectedTag.equals(currentStyleRange.data)) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("Error at position #").append(currentPosition).append(" - closing ").append(wholeExpectedTag).append(" tag found but "); |
| sb.append(currentStyleRange.data).append(" tag expected !"); |
| throw new RuntimeException(sb.toString()); |
| } |
| currentStyleRange.length = currentPosition - currentStyleRange.start; |
| listOfStyles.add(currentStyleRange); |
| |
| } |
| |
| /** |
| * Process begin italic. |
| */ |
| private void processBeginItalic() { |
| final StyleRange currentStyleRange = new StyleRange(); |
| currentStyleRange.start = currentPosition; |
| currentStyleRange.length = 0; |
| currentStyleRange.fontStyle = SWT.ITALIC; |
| currentStyleRange.data = "</i>"; |
| stack.push(currentStyleRange); |
| } |
| |
| /** |
| * Process begin underline. |
| */ |
| private void processBeginUnderline() { |
| final StyleRange currentStyleRange = new StyleRange(); |
| currentStyleRange.start = currentPosition; |
| currentStyleRange.length = 0; |
| currentStyleRange.fontStyle = SWT.NONE; |
| currentStyleRange.underline = true; |
| currentStyleRange.data = "</u>"; |
| stack.push(currentStyleRange); |
| } |
| |
| /** |
| * Process begin size. |
| */ |
| private void processBeginSize() { |
| final StyleRange currentStyleRange = new StyleRange(); |
| currentStyleRange.start = currentPosition; |
| currentStyleRange.length = 0; |
| currentStyleRange.fontStyle = SWT.NONE; |
| currentStyleRange.font = computeFont(); |
| currentStyleRange.data = "</size>"; |
| stack.push(currentStyleRange); |
| } |
| |
| /** |
| * Compute font. |
| * |
| * @return the font |
| */ |
| private Font computeFont() { |
| final String fontSize = currentTag.toString().toLowerCase().replace("size=", ""); |
| if (fontSize.length() == 0) { |
| throw new RuntimeException("Argument size is empty !"); |
| } |
| int newSize = defaultHeight; |
| if (fontSize.startsWith("+")) { |
| final int delta = Integer.valueOf(fontSize.substring(1)); |
| newSize += delta; |
| } else if (fontSize.startsWith("-")) { |
| final int delta = Integer.valueOf(fontSize.substring(1)); |
| newSize -= delta; |
| } |
| |
| final FontData fd = styledText.getFont().getFontData()[0]; |
| final Font newFont = new Font(styledText.getDisplay(), fd.getName(), newSize, SWT.NONE); |
| styledText.addListener(SWT.Dispose, new Listener() { |
| @Override |
| public void handleEvent(final Event event) { |
| newFont.dispose(); |
| } |
| }); |
| return newFont; |
| } |
| |
| /** |
| * Process begin color. |
| */ |
| private void processBeginColor() { |
| final StyleRange currentStyleRange = new StyleRange(); |
| currentStyleRange.start = currentPosition; |
| currentStyleRange.length = 0; |
| currentStyleRange.fontStyle = SWT.NONE; |
| currentStyleRange.foreground = computeColor(); |
| currentStyleRange.data = "</color>"; |
| stack.push(currentStyleRange); |
| } |
| |
| /** |
| * Compute color. |
| * |
| * @return the color |
| */ |
| private Color computeColor() { |
| final String fontColor = currentTag.toString().toLowerCase().replace("color=", "").replace("background", ""); |
| if (fontColor.length() == 0) { |
| throw new RuntimeException("Argument color is empty !"); |
| } |
| |
| int red, green, blue; |
| if (fontColor.startsWith("#")) { |
| final String hexa = fontColor.substring(1); |
| if (hexa.length() != 6) { |
| throw new RuntimeException("Argument [" + hexa + "] is not valid !"); |
| } |
| try { |
| red = Integer.parseInt(hexa.substring(0, 2).toLowerCase(), 16); |
| green = Integer.parseInt(hexa.substring(2, 4).toLowerCase(), 16); |
| blue = Integer.parseInt(hexa.substring(4, 6).toLowerCase(), 16); |
| } catch (final NumberFormatException nfe) { |
| throw new RuntimeException("Argument [" + hexa + "] is not valid !"); |
| } |
| } else if (fontColor.indexOf(',') > -1) { |
| final String[] args = fontColor.split(","); |
| if (args.length != 3) { |
| throw new RuntimeException("Argument [" + fontColor + "] is not valid !"); |
| } |
| try { |
| red = Integer.parseInt(args[0]); |
| green = Integer.parseInt(args[1]); |
| blue = Integer.parseInt(args[2]); |
| } catch (final NumberFormatException nfe) { |
| throw new RuntimeException("Argument [" + fontColor + "] is not valid !"); |
| } |
| } else { |
| final Integer[] rgb = HTML_CODES.get(fontColor.toLowerCase()); |
| if (rgb == null) { |
| red = 0; |
| green = 0; |
| blue = 0; |
| } else { |
| red = rgb[0]; |
| green = rgb[1]; |
| blue = rgb[2]; |
| } |
| } |
| final Color color = new Color(styledText.getDisplay(), red, green, blue); |
| styledText.addListener(SWT.Dispose, new Listener() { |
| @Override |
| public void handleEvent(final Event event) { |
| color.dispose(); |
| } |
| }); |
| return color; |
| } |
| |
| /** |
| * Process begin background color. |
| */ |
| private void processBeginBackgroundColor() { |
| final StyleRange currentStyleRange = new StyleRange(); |
| currentStyleRange.start = currentPosition; |
| currentStyleRange.length = 0; |
| currentStyleRange.fontStyle = SWT.NONE; |
| currentStyleRange.background = computeColor(); |
| currentStyleRange.data = "</backgroundcolor>"; |
| stack.push(currentStyleRange); |
| } |
| |
| } |