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}