001/** 002 * Copyright (c) 2008-2014 Ardor Labs, Inc. 003 * 004 * This file is part of Ardor3D. 005 * 006 * Ardor3D is free software: you can redistribute it and/or modify it 007 * under the terms of its license which may be found in the accompanying 008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>. 009 */ 010 011package com.ardor3d.extension.ui.text.parser; 012 013import java.util.ArrayList; 014import java.util.Collections; 015import java.util.Comparator; 016import java.util.Iterator; 017import java.util.LinkedList; 018import java.util.List; 019import java.util.StringTokenizer; 020 021import com.ardor3d.extension.ui.text.StyleConstants; 022import com.ardor3d.extension.ui.text.StyleSpan; 023import com.ardor3d.math.ColorRGBA; 024import com.ardor3d.math.type.ReadOnlyColorRGBA; 025 026public class ForumLikeMarkupParser implements StyleParser { 027 028 Comparator<StyleSpan> endSorter = new Comparator<StyleSpan>() { 029 @Override 030 public int compare(final StyleSpan o1, final StyleSpan o2) { 031 return o1.getSpanStart() + o1.getSpanLength() - (o2.getSpanStart() + o2.getSpanLength()); 032 } 033 }; 034 035 @Override 036 public String parseStyleSpans(final String text, final List<StyleSpan> store) { 037 final StringBuilder rVal = new StringBuilder(""); 038 int index = 0; 039 TagStatus tagStatus = TagStatus.NONE; 040 String currTagText = ""; 041 final LinkedList<StyleSpan> buildingSpans = new LinkedList<>(); 042 final StringTokenizer st = new StringTokenizer(text, "[]\\", true); 043 String token; 044 while (st.hasMoreTokens()) { 045 token = st.nextToken(); 046 // escape char 047 if (tagStatus == TagStatus.NONE && "\\".equals(token)) { 048 if (st.hasMoreTokens()) { 049 token = st.nextToken(); 050 if ("[".equals(token) || "]".equals(token)) { 051 rVal.append(token); 052 index++; 053 continue; 054 } else { 055 rVal.append('\\'); 056 rVal.append(token); 057 index += token.length() + 1; 058 continue; 059 } 060 } else { 061 rVal.append('\\'); 062 index++; 063 continue; 064 } 065 } 066 067 // start token 068 else if (tagStatus == TagStatus.NONE && "[".equals(token)) { 069 tagStatus = TagStatus.START_TAG; 070 continue; 071 } 072 073 else if (tagStatus == TagStatus.START_TAG) { 074 currTagText = token; 075 tagStatus = TagStatus.IN_TAG; 076 continue; 077 } 078 079 // end token 080 else if (tagStatus == TagStatus.IN_TAG && "]".equals(token)) { 081 tagStatus = TagStatus.NONE; 082 // interpret tag: 083 // BOLD 084 if ("b".equalsIgnoreCase(currTagText)) { 085 // start a new bold span 086 buildingSpans.add(new StyleSpan(StyleConstants.KEY_BOLD, Boolean.TRUE, index, 0)); 087 } else if ("/b".equalsIgnoreCase(currTagText)) { 088 // find last BOLD entry and add length 089 endSpan(StyleConstants.KEY_BOLD, store, index, buildingSpans); 090 } 091 092 // ITALICS 093 else if ("i".equalsIgnoreCase(currTagText)) { 094 // start a new italics span 095 buildingSpans.add(new StyleSpan(StyleConstants.KEY_ITALICS, Boolean.TRUE, index, 0)); 096 } else if ("/i".equalsIgnoreCase(currTagText)) { 097 // find last ITALICS entry and add length 098 endSpan(StyleConstants.KEY_ITALICS, store, index, buildingSpans); 099 } 100 101 // COLOR 102 else if (currTagText.toLowerCase().startsWith("c=")) { 103 // start a new color span 104 try { 105 // parse a color 106 final String c = currTagText.substring(2); 107 buildingSpans.add(new StyleSpan(StyleConstants.KEY_COLOR, ColorRGBA.parseColor(c, null), index, 108 0)); 109 } catch (final Exception e) { 110 e.printStackTrace(); 111 } 112 } else if ("/c".equalsIgnoreCase(currTagText)) { 113 // find last BOLD entry and add length 114 endSpan(StyleConstants.KEY_COLOR, store, index, buildingSpans); 115 } 116 117 // SIZE 118 else if (currTagText.toLowerCase().startsWith("size=")) { 119 // start a new size span 120 try { 121 // parse a size 122 final int i = Integer.parseInt(currTagText.substring(5)); 123 buildingSpans.add(new StyleSpan(StyleConstants.KEY_SIZE, i, index, 0)); 124 } catch (final Exception e) { 125 e.printStackTrace(); 126 } 127 } else if ("/size".equalsIgnoreCase(currTagText)) { 128 // find last SIZE entry and add length 129 endSpan(StyleConstants.KEY_SIZE, store, index, buildingSpans); 130 } 131 132 // FAMILY 133 else if (currTagText.toLowerCase().startsWith("f=")) { 134 // start a new family span 135 final String family = currTagText.substring(2); 136 buildingSpans.add(new StyleSpan(StyleConstants.KEY_FAMILY, family, index, 0)); 137 } else if ("/f".equalsIgnoreCase(currTagText)) { 138 // find last FAMILY entry and add length 139 endSpan(StyleConstants.KEY_FAMILY, store, index, buildingSpans); 140 } else { 141 // not really a tag, so put it back. 142 rVal.append('['); 143 rVal.append(currTagText); 144 rVal.append(']'); 145 tagStatus = TagStatus.NONE; 146 } 147 148 currTagText = ""; 149 continue; 150 } 151 152 // anything else 153 rVal.append(token); 154 index += token.length(); 155 } 156 157 // close any remaining open tags 158 while (!buildingSpans.isEmpty()) { 159 final StyleSpan span = buildingSpans.getLast(); 160 endSpan(span.getStyle(), store, index, buildingSpans); 161 } 162 163 // return plain text 164 return rVal.toString(); 165 } 166 167 private void endSpan(final String key, final List<StyleSpan> store, final int index, 168 final LinkedList<StyleSpan> buildingSpans) { 169 for (final Iterator<StyleSpan> it = buildingSpans.descendingIterator(); it.hasNext();) { 170 final StyleSpan next = it.next(); 171 if (key.equals(next.getStyle())) { 172 next.setSpanLength(index - next.getSpanStart()); 173 store.add(next); 174 it.remove(); 175 break; 176 } 177 } 178 } 179 180 @Override 181 public String addStyleMarkup(final String plainText, final List<StyleSpan> spans) { 182 if (spans.isEmpty()) { 183 return plainText; 184 } 185 186 // list of spans, sorted by start index 187 final List<StyleSpan> starts = new ArrayList<>(); 188 starts.addAll(spans); 189 Collections.sort(starts); 190 191 // list of spans, to be sorted by end index 192 final List<StyleSpan> ends = new LinkedList<>(); 193 194 final StringBuilder builder = new StringBuilder(); 195 196 // go through all chars and add starts and ends 197 for (int index = 0, max = plainText.length(); index < max; index++) { 198 // close markup 199 while (!ends.isEmpty()) { 200 final StyleSpan span = ends.get(0); 201 if (span.getSpanStart() + span.getSpanLength() == index) { 202 builder.append(getMarkup(span, true)); 203 ends.remove(0); 204 } else { 205 break; 206 } 207 } 208 209 // add starts 210 while (!starts.isEmpty()) { 211 final StyleSpan span = starts.get(0); 212 if (span.getSpanStart() == index) { 213 builder.append(getMarkup(span, false)); 214 ends.add(span); 215 starts.remove(0); 216 Collections.sort(ends, endSorter); 217 } else { 218 break; 219 } 220 } 221 222 builder.append(plainText.charAt(index)); 223 } 224 225 // close any remaining markup: 226 while (!ends.isEmpty()) { 227 final StyleSpan span = ends.get(0); 228 builder.append(getMarkup(span, true)); 229 ends.remove(0); 230 } 231 232 return builder.toString(); 233 } 234 235 private String getMarkup(final StyleSpan span, final boolean end) { 236 if (StyleConstants.KEY_BOLD.equalsIgnoreCase(span.getStyle())) { 237 return end ? "[/b]" : "[b]"; 238 } else if (StyleConstants.KEY_ITALICS.equalsIgnoreCase(span.getStyle())) { 239 return end ? "[/i]" : "[i]"; 240 } else if (StyleConstants.KEY_FAMILY.equalsIgnoreCase(span.getStyle())) { 241 return end ? "[/f]" : "[f=" + span.getValue() + "]"; 242 } else if (StyleConstants.KEY_SIZE.equalsIgnoreCase(span.getStyle())) { 243 return end ? "[/size]" : "[size=" + span.getValue() + "]"; 244 } else if (StyleConstants.KEY_COLOR.equalsIgnoreCase(span.getStyle()) && span.getValue() instanceof ColorRGBA) { 245 return end ? "[/c]" : "[c=" + ((ReadOnlyColorRGBA) span.getValue()).asHexRRGGBBAA() + "]"; 246 } 247 248 return ""; 249 } 250 251 enum TagStatus { 252 NONE, START_TAG, IN_TAG 253 } 254}