001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.graphics.Color; 004import com.badlogic.gdx.graphics.Colors; 005import com.badlogic.gdx.math.MathUtils; 006import regexodus.Matcher; 007import regexodus.Pattern; 008import squidpony.StringKit; 009import squidpony.panel.IColoredString; 010import squidpony.panel.IMarkup; 011 012/** 013 * GDXMarkup implements IMarkup for libGDX Color objects, and can start blocks of markup that libGDX understands that 014 * display text in a given Color. Typically, the singleton {@link #instance} would be passed to a class that uses 015 * IMarkup of Color, and then anything else will be handled internally as Colors are given to the using class. This does 016 * extend GDX's markup to handle bold and italic options for text; this only works if you are using a {@link TextFamily} 017 * as your {@link TextCellFactory}, such as {@link DefaultResources#getSlabFamily()}, and only if you use 018 * {@link #colorString(CharSequence)} or {@link #styleString(CharSequence)} to generate a value that can be drawn later. 019 * You can use {@link #colorStringOnly(CharSequence)} if you aren't using a TextFamily; it ignores bold and italic style 020 * tags and removes them from the IColoredString it returns, but otherwise colors and case-adjusts text as tags say. 021 * <br> 022 * The notation for colors is the same as in the rest of libGDX, but if you make an IColoredString with colorString(), 023 * it doesn't need any flag to be changed on your BitmapFont (like it does for GDX markup normally). This notation looks 024 * like {@code [#FF00FF]Magenta text here[]}, which starts a tag for a hex color, uses the hex value for bright magenta, 025 * then contains the text "Magenta text here" which will be shown in bright magenta, followed by "[]" to change the 026 * color (and style, see next) back to the default white (and normal style). You can also use the names of colors, as 027 * defined in the documentation for every {@link SColor}, and some libGDX Color values as well; this looks like 028 * {@code [Inside Of A Bottle]Gray text[]} to produce the words "Gray text" with the color 029 * {@link SColor#INSIDE_OF_A_BOTTLE} (note that the docs for that SColor say what the precise name is, and case needs to 030 * match; you can also look up the {@link SColor#name} field). Another option for colors is to use hue, saturation, 031 * value, and optionally opacity as 3 or 4 floats, in that order; this looks like {@code [@ 0 0.7 0.96]} (hue, 032 * saturation, and value, with opacity implicitly 1) or {@code [@ 0 0.7 0.96 0.8]} (0.8 opacity is a bit translucent). 033 * The HSV markup option is an addition to libGDX's syntax; any number of spaces/tabs can be used between HSV components 034 * and the space between @ and the first component is optional. You can use {@code [[} to escape an opening bracket, and 035 * {@code []} to reset formatting. As an addition to GDX color markup, you can change case with {@code [!]} to make text 036 * ALL CAPS or {@code [=]} to make it lower-case (encountering a tag toggles it, so {@code [!]yelling[!] are we?} would 037 * become {@code YELLING are we?}), using {@code []} to reset all markup or {@code [,]} to reset just case and font 038 * style (see next) markup. If using a TextFamily such as {@link DefaultResources#getLeanFamily()}, you can toggle the 039 * font style as bold with {@code [*]} and as italic with {@code [/]}. If bold is on when this encounters another bold 040 * tag, it will turn bold off; the same is true for italic. These formatting styles can overlap and do not need to be 041 * nested as in HTML; this notation is valid: {@code [*]bold, [/]bold and italic, [*] just italic,[] plain}. While 042 * {@code []} resets both color, case, and style to white color, normal case, and regular style, there is also 043 * {@code [,]} to reset only style/case, or {@code [WHITE]} to reset only color (to white). 044 * <br> 045 * Created by Tommy Ettinger on 1/23/2016. 046 */ 047public class GDXMarkup implements IMarkup<Color>{ 048 public static GDXMarkup instance = new GDXMarkup(); 049 public GDXMarkup() 050 { 051 052 } 053 private static String floatToHex(float f) 054 { 055 //String s = Integer.toHexString(MathUtils.round(f * 255)); 056 //if(s.length() < 2) return "0" + s; 057 //else return s; 058 int r = MathUtils.round(f * 255); 059 return String.valueOf(StringKit.hexDigits[r >>> 4 & 15]) + StringKit.hexDigits[r & 15]; 060 } 061 @Override 062 public String getMarkup(Color value) { 063 return "[#" + floatToHex(value.r) + floatToHex(value.g) + floatToHex(value.b) + floatToHex(value.a) + "]"; 064 } 065 066 @Override 067 public String closeMarkup() { 068 return "[]"; 069 } 070 071 private final Matcher markupMatcher = Pattern.compile("({=p}[^\\[]+)|(?:\\[({=e}\\[))|(?:\\[#({=h}[0-9A-Fa-f]{6,8})\\])|(?:\\[@\\s*({=q}({=qh}[0-9]*\\.?[0-9]+)\\s+({=qs}[0-9]*\\.?[0-9]+)\\s+({=qv}[0-9]*\\.?[0-9]+)(?:\\s+({=qo}[0-9]*\\.?[0-9]+))?)\\])|(?:\\[({=b}\\*)\\])|(?:\\[({=i}/)\\])|(?:\\[({=U}!)\\])|(?:\\[({=L}=)\\])|(?:\\[({=u},)\\])|(?:\\[({=n}[^\\]]+?)\\])|(?:\\[({=r}\\]))").matcher(); 072 private static final char BOLD = '\u4000', ITALIC = '\u8000', REGULAR = '\0'; 073 private final StringBuilder sb = new StringBuilder(128); 074 075 /** 076 * Removes all SquidLib and libGDX markup from the given {@code markupString} except for {@code [[} to escape a left 077 * bracket, returning the result as a new StringBuilder. 078 * @param markupString a String or other CharSequence containing color and/or style markup tags 079 * @return markupString without color, case, or style markup tags, only keeping text and escapes for left brackets. 080 */ 081 public StringBuilder removeMarkup(final CharSequence markupString) 082 { 083 StringBuilder cs = new StringBuilder(markupString.length()); 084 markupMatcher.setTarget(markupString); 085 while (markupMatcher.find()) 086 { 087 if(!markupMatcher.getGroup("p", cs) && markupMatcher.isCaptured("e")) 088 { 089 cs.append("[["); 090 } 091 } 092 return cs; 093 } 094 095 /** 096 * Takes a CharSequence (such as a String or StringBuilder) that contains the markup this class understands, and 097 * produces an IColoredString (of Color) with the color markup tags used to mark text in colors in the resulting 098 * IColoredString, and any style markup tags used to mark sections of text as bold or italic for a TextFamily to 099 * render (normal TextCellFactory rendering may show bold/italic text as gibberish). 100 * @param markupString a String or other CharSequence containing color and/or style markup tags 101 * @return an IColoredString (of Color) with all the markup applied and removed from the text after applying 102 */ 103 public IColoredString<Color> colorString(final CharSequence markupString) 104 { 105 markupMatcher.setTarget(markupString); 106 IColoredString<Color> cs = new IColoredString.Impl<>(); 107 Color current = Color.WHITE; 108 char mod = REGULAR; 109 int casing = 0; 110 while (markupMatcher.find()) 111 { 112 if(markupMatcher.getGroup("p", sb)) 113 { 114 switch (casing) { 115 case -1: 116 for (int i = 0; i < sb.length(); i++) { 117 sb.setCharAt(i, Character.toLowerCase((char) (sb.charAt(i) | mod))); 118 } 119 break; 120 case 1: 121 for (int i = 0; i < sb.length(); i++) { 122 sb.setCharAt(i, Character.toUpperCase((char) (sb.charAt(i) | mod))); 123 } 124 break; 125 default: 126 for (int i = 0; i < sb.length(); i++) { 127 sb.setCharAt(i, (char) (sb.charAt(i) | mod)); 128 } 129 break; 130 } 131 cs.append(sb.toString(), current); 132 } 133 else if(markupMatcher.isCaptured("e")) 134 { 135 cs.append((char) ('[' | mod), current); 136 } 137 else if(markupMatcher.isCaptured("r")) 138 { 139 current = Color.WHITE; 140 mod = REGULAR; 141 casing = 0; 142 } 143 else if(markupMatcher.isCaptured("u")) 144 { 145 mod = REGULAR; 146 casing = 0; 147 } 148 else if(markupMatcher.getGroup("h", sb)) 149 { 150 if(sb.length() == 6) 151 current = DefaultResources.getSCC().get(StringKit.intFromHex(sb) << 8 | 0xFF); 152 else 153 current = DefaultResources.getSCC().get(StringKit.intFromHex(sb)); 154 } 155 else if(markupMatcher.getGroup("n", sb)) 156 { 157 current = Colors.get(sb.toString()); 158 } 159 else if(markupMatcher.isCaptured("q")) 160 { 161 current = markupMatcher.isCaptured("qo") 162 ? DefaultResources.getSCC().getHSV( 163 Float.parseFloat(markupMatcher.group("qh")), 164 Float.parseFloat(markupMatcher.group("qs")), 165 Float.parseFloat(markupMatcher.group("qv")), 166 Float.parseFloat(markupMatcher.group("qo"))) 167 : DefaultResources.getSCC().getHSV( 168 Float.parseFloat(markupMatcher.group("qh")), 169 Float.parseFloat(markupMatcher.group("qs")), 170 Float.parseFloat(markupMatcher.group("qv"))); 171 } 172 else if(markupMatcher.isCaptured("b")) 173 { 174 mod ^= BOLD; 175 } 176 else if(markupMatcher.isCaptured("i")) 177 { 178 mod ^= ITALIC; 179 } 180 else if(markupMatcher.isCaptured("U")) 181 { 182 casing = casing == 1 ? 0 : 1; 183 } 184 else if(markupMatcher.isCaptured("L")) 185 { 186 casing = casing == -1 ? 0 : -1; 187 } 188 sb.setLength(0); 189 } 190 return cs; 191 } 192 /** 193 * Takes a CharSequence (such as a String or StringBuilder) that contains the markup this class understands, and 194 * produces an IColoredString (of Color) with the color markup tags used to mark text in colors in the resulting 195 * IColoredString, but with both bold and italic style markup tags ignored (so normal TextCellFactory objects can 196 * render the text correctly). Case markup is still used as in other methods here, and all markup tags, including 197 * those for bold and italic styles, will be removed (so you don't need to manually remove styles that can't be 198 * shown by a TextCellFactory). 199 * @param markupString a String or other CharSequence containing color and/or case markup tags 200 * @return an IColoredString (of Color) with all the markup applied and removed from the text after applying 201 */ 202 public IColoredString<Color> colorStringOnly(final CharSequence markupString) 203 { 204 markupMatcher.setTarget(markupString); 205 IColoredString<Color> cs = new IColoredString.Impl<>(); 206 Color current = Color.WHITE; 207 int casing = 0; 208 while (markupMatcher.find()) 209 { 210 if(markupMatcher.getGroup("p", sb)) 211 { 212 switch (casing) { 213 case -1: 214 for (int i = 0; i < sb.length(); i++) { 215 sb.setCharAt(i, Character.toLowerCase(sb.charAt(i))); 216 } 217 break; 218 case 1: 219 for (int i = 0; i < sb.length(); i++) { 220 sb.setCharAt(i, Character.toUpperCase(sb.charAt(i))); 221 } 222 break; 223 } 224 cs.append(sb.toString(), current); 225 } 226 else if(markupMatcher.isCaptured("e")) 227 { 228 cs.append('[', current); 229 } 230 else if(markupMatcher.isCaptured("r")) 231 { 232 current = Color.WHITE; 233 casing = 0; 234 } 235 else if(markupMatcher.isCaptured("u")) 236 { 237 casing = 0; 238 } 239 else if(markupMatcher.getGroup("h", sb)) 240 { 241 if(sb.length() == 6) 242 current = DefaultResources.getSCC().get(StringKit.intFromHex(sb) << 8 | 0xFF); 243 else 244 current = DefaultResources.getSCC().get(StringKit.intFromHex(sb)); 245 } 246 else if(markupMatcher.getGroup("n", sb)) 247 { 248 current = Colors.get(sb.toString()); 249 } 250 else if(markupMatcher.isCaptured("q")) 251 { 252 current = markupMatcher.isCaptured("qo") 253 ? DefaultResources.getSCC().getHSV( 254 Float.parseFloat(markupMatcher.group("qh")), 255 Float.parseFloat(markupMatcher.group("qs")), 256 Float.parseFloat(markupMatcher.group("qv")), 257 Float.parseFloat(markupMatcher.group("qo"))) 258 : DefaultResources.getSCC().getHSV( 259 Float.parseFloat(markupMatcher.group("qh")), 260 Float.parseFloat(markupMatcher.group("qs")), 261 Float.parseFloat(markupMatcher.group("qv"))); 262 } 263// else if(markupMatcher.isCaptured("b") || markupMatcher.isCaptured("i")) 264// { 265// markupMatcher.getGroup(0, sb); 266// } 267 else if(markupMatcher.isCaptured("U")) 268 { 269 casing = casing == 1 ? 0 : 1; 270 } 271 else if(markupMatcher.isCaptured("L")) 272 { 273 casing = casing == -1 ? 0 : -1; 274 } 275 sb.setLength(0); 276 } 277 return cs; 278 } 279 280 /** 281 * Similar to {@link #colorString(CharSequence)}, but leaves color tags as they are and only uses case and style 282 * tags, like {@code [*]} for bold, {@code [/]} for italic, {@code [!]} for ALL CAPS and {@code [=]} for lower-case. 283 * The StringBuilder this returns can be converted to a String or used directly for further modification, but styles 284 * will probably only render correctly if using a TextFamily. You should be aware that if {@code []} is used to 285 * reset both color and style in the given markupString, then only the style will be reset here but the {@code []} 286 * will be removed, which may affect colors if the result is later given to something that expects color markup to 287 * also have been closed. A solution for this is to use {@code [,]} to reset only styles, and to avoid using 288 * {@code []} to change color by explicitly using {@code [WHITE]} to set the text color back to pure white. This way 289 * is only necessary if you have color markup in the markupString you pass to this method. 290 * @param markupString a String containing color markup (which is left as-is) and/or case/style markup (which is used) 291 * @return a StringBuilder based on markupString that has the case/style markup applied and other markup left there 292 */ 293 public StringBuilder styleString(final CharSequence markupString) 294 { 295 markupMatcher.setTarget(markupString); 296 char mod = REGULAR; 297 int casing = 0; 298 StringBuilder fsb = new StringBuilder(markupString.length()); 299 while (markupMatcher.find()) 300 { 301 if(markupMatcher.getGroup("p", sb)) 302 { 303 switch (casing) { 304 case -1: 305 for (int i = 0; i < sb.length(); i++) { 306 sb.setCharAt(i, Character.toLowerCase((char) (sb.charAt(i) | mod))); 307 } 308 break; 309 case 1: 310 for (int i = 0; i < sb.length(); i++) { 311 sb.setCharAt(i, Character.toUpperCase((char) (sb.charAt(i) | mod))); 312 } 313 break; 314 default: 315 for (int i = 0; i < sb.length(); i++) { 316 sb.setCharAt(i, (char) (sb.charAt(i) | mod)); 317 } 318 break; 319 } 320 fsb.append(sb); 321 } 322 else if(markupMatcher.isCaptured("e")) 323 { 324 fsb.append((char) ('[' | mod)); 325 } 326 else if(markupMatcher.isCaptured("r") || markupMatcher.isCaptured("u")) 327 { 328 mod = REGULAR; 329 } 330 else if(markupMatcher.isCaptured("h") || markupMatcher.isCaptured("n") || markupMatcher.isCaptured("q")) 331 { 332 markupMatcher.getGroup(0, fsb); 333 } 334 else if(markupMatcher.isCaptured("b")) 335 { 336 mod ^= BOLD; 337 } 338 else if(markupMatcher.isCaptured("i")) 339 { 340 mod ^= ITALIC; 341 } 342 else if(markupMatcher.isCaptured("U")) 343 { 344 casing = casing == 1 ? 0 : 1; 345 } 346 else if(markupMatcher.isCaptured("L")) 347 { 348 casing = casing == -1 ? 0 : -1; 349 } 350 sb.setLength(0); 351 352 } 353 return fsb; 354 } 355 356 /** 357 * Directly styles one char value, toggling its bold state if {@code bold} is true, and toggling its italic state if 358 * {@code italic} is true. When either or both of bold or italic are enabled in a char, that char will be at some 359 * unrelated section of Unicode, and the char will be hard to identify unless you call {@link #unstyleChar(char)} on 360 * it or render it with a TextFamily (which will render bold and italic chars as the correct glyph and style). 361 * @param basis the char to potentially make bold or italic; can be bold or italic already 362 * @param bold if true, the bold-ness of basis will be toggled; if false, does not change bold data 363 * @param italic if true, the italic-ness of basis will be toggled; if false, does not change italic data 364 * @return a char that will look like basis when rendered appropriately but with bold and italic settings applied 365 */ 366 public char styleChar(char basis, boolean bold, boolean italic) 367 { 368 return (char) (basis ^ (bold ? '\u4000' : '\0') ^ (italic ? '\u8000' : '\0')); 369 } 370 371 /** 372 * If given a char that has added style information for bold/italic modes (colors aren't stored in char data), this 373 * removes the bold/italic data and makes the char what it will be rendered as in a normal font (not a special 374 * TextFamily, which renders later sections of Unicode as bold and/or italic). 375 * @param styled a char that should have any bold or italic data set to normal (non-bold, non-italic) 376 * @return a char that will not be bold or italic 377 */ 378 public char unstyleChar(char styled) 379 { 380 return (char) (styled & '\u3fff'); 381 } 382 383 384 /** 385 * Takes a CharSequence (such as a String or StringBuilder) that contains the markup this class understands, and 386 * produces a StringBuilder with the color markup tags in {@code markupString} all changed to libGDX-usable color 387 * markup, and any style markup tags used to mark sections of text as bold or italic for a TextFamily to 388 * render (normal TextCellFactory rendering may show bold/italic text as gibberish). This needs markup to be enabled 389 * on any BitmapFont that renders it, by setting 390 * {@link com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData#markupEnabled} to true. 391 * @param markupString a String or other CharSequence containing color and/or style markup tags 392 * @return a new StringBuilder with all the markup applied; only libGDX-usable markup will remain 393 */ 394 public StringBuilder colorStringMarkup(final CharSequence markupString) 395 { 396 markupMatcher.setTarget(markupString); 397 StringBuilder cs = new StringBuilder(markupString.length()); 398 char mod = REGULAR; 399 int casing = 0; 400 while (markupMatcher.find()) 401 { 402 if(markupMatcher.getGroup("p", sb)) 403 { 404 switch (casing) { 405 case -1: 406 for (int i = 0; i < sb.length(); i++) { 407 sb.setCharAt(i, Character.toLowerCase((char) (sb.charAt(i) | mod))); 408 } 409 break; 410 case 1: 411 for (int i = 0; i < sb.length(); i++) { 412 sb.setCharAt(i, Character.toUpperCase((char) (sb.charAt(i) | mod))); 413 } 414 break; 415 default: 416 for (int i = 0; i < sb.length(); i++) { 417 sb.setCharAt(i, (char) (sb.charAt(i) | mod)); 418 } 419 break; 420 } 421 cs.append(sb); 422 } 423 else if(markupMatcher.isCaptured("e")) 424 { 425 if(mod == 0) 426 cs.append("[["); 427 else 428 cs.append((char) ('[' | mod)); 429 } 430 else if(markupMatcher.isCaptured("r")) 431 { 432 cs.append("[]"); 433 mod = REGULAR; 434 casing = 0; 435 } 436 else if(markupMatcher.isCaptured("u")) 437 { 438 mod = REGULAR; 439 casing = 0; 440 } 441 else if(markupMatcher.getGroup("h", sb)) 442 { 443 if(sb.length() == 6) 444 cs.append("[#").append(sb).append("FF]"); 445 else if(sb.length() == 8) 446 cs.append("[#").append(sb).append(']'); 447 } 448 else if(markupMatcher.getGroup("n", sb)) 449 { 450 cs.append('[').append(sb).append(']'); 451 } 452 else if(markupMatcher.isCaptured("q")) 453 { 454 cs.append("[#"); 455 StringKit.appendHex(cs, markupMatcher.isCaptured("qo") 456 ? SColor.intGetHSV( 457 Float.parseFloat(markupMatcher.group("qh")), 458 Float.parseFloat(markupMatcher.group("qs")), 459 Float.parseFloat(markupMatcher.group("qv")), 460 Float.parseFloat(markupMatcher.group("qo"))) 461 : SColor.intGetHSV( 462 Float.parseFloat(markupMatcher.group("qh")), 463 Float.parseFloat(markupMatcher.group("qs")), 464 Float.parseFloat(markupMatcher.group("qv")), 1f)); 465 cs.append(']'); 466 } 467 else if(markupMatcher.isCaptured("b")) 468 { 469 mod ^= BOLD; 470 } 471 else if(markupMatcher.isCaptured("i")) 472 { 473 mod ^= ITALIC; 474 } 475 else if(markupMatcher.isCaptured("U")) 476 { 477 casing = casing == 1 ? 0 : 1; 478 } 479 else if(markupMatcher.isCaptured("L")) 480 { 481 casing = casing == -1 ? 0 : -1; 482 } 483 sb.setLength(0); 484 } 485 return cs; 486 } 487 /** 488 * Takes a CharSequence (such as a String or StringBuilder) that contains the markup this class understands, and 489 * produces a StringBuilder with the color markup tags in {@code markupString} all changed to libGDX-usable color 490 * markup, but with both bold and italic style markup tags ignored (so normal TextCellFactory objects can 491 * render the text correctly). Case markup is still used as in other methods here, and all markup tags that libGDX 492 * can't use, including those for bold and italic styles, will be removed (so you don't need to manually remove 493 * styles that can't be shown by a TextCellFactory). This needs markup to be enabled on any BitmapFont that renders 494 * it, by setting {@link com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData#markupEnabled} to true. 495 * @param markupString a String or other CharSequence containing color and/or case markup tags (style markup will be removed) 496 * @return a new StringBuilder with all the color and case markup applied; only libGDX-usable markup will remain 497 */ 498 public StringBuilder colorStringOnlyMarkup(final CharSequence markupString) 499 { 500 markupMatcher.setTarget(markupString); 501 StringBuilder cs = new StringBuilder(markupString.length()); 502 int casing = 0; 503 while (markupMatcher.find()) 504 { 505 if(markupMatcher.getGroup("p", sb)) 506 { 507 switch (casing) { 508 case -1: 509 for (int i = 0; i < sb.length(); i++) { 510 sb.setCharAt(i, Character.toLowerCase(sb.charAt(i))); 511 } 512 break; 513 case 1: 514 for (int i = 0; i < sb.length(); i++) { 515 sb.setCharAt(i, Character.toUpperCase(sb.charAt(i))); 516 } 517 break; 518 } 519 cs.append(sb); 520 } 521 else if(markupMatcher.isCaptured("e")) 522 { 523 cs.append("[["); 524 } 525 else if(markupMatcher.isCaptured("r")) 526 { 527 cs.append("[]"); 528 casing = 0; 529 } 530 else if(markupMatcher.isCaptured("u")) 531 { 532 casing = 0; 533 } 534 else if(markupMatcher.getGroup("h", sb)) 535 { 536 if(sb.length() == 6) 537 cs.append("[#").append(sb).append("FF]"); 538 else if(sb.length() == 8) 539 cs.append("[#").append(sb).append(']'); 540 } 541 else if(markupMatcher.getGroup("n", sb)) 542 { 543 cs.append('[').append(sb).append(']'); 544 } 545 else if(markupMatcher.isCaptured("q")) 546 { 547 cs.append("[#"); 548 StringKit.appendHex(cs, markupMatcher.isCaptured("qo") 549 ? SColor.intGetHSV( 550 Float.parseFloat(markupMatcher.group("qh")), 551 Float.parseFloat(markupMatcher.group("qs")), 552 Float.parseFloat(markupMatcher.group("qv")), 553 Float.parseFloat(markupMatcher.group("qo"))) 554 : SColor.intGetHSV( 555 Float.parseFloat(markupMatcher.group("qh")), 556 Float.parseFloat(markupMatcher.group("qs")), 557 Float.parseFloat(markupMatcher.group("qv")), 1f)); 558 cs.append(']'); 559 } 560 else if(markupMatcher.isCaptured("U")) 561 { 562 casing = casing == 1 ? 0 : 1; 563 } 564 else if(markupMatcher.isCaptured("L")) 565 { 566 casing = casing == -1 ? 0 : -1; 567 } 568 sb.setLength(0); 569 } 570 return cs; 571 } 572 573 574 /* 575 @Override 576 public String escape(String initialText) 577 { 578 return initialText.replace("[", "[["); 579 } 580 */ 581}