0

I'm writing tag->HTML parser - user type some text with custom tags (something between BB and Markdown, like [b] or [#RRGGBB]), then it's parsing to HTML - so [#FF0000]red text[/#] turns into <span style='color:#FF0000;'>red text</strong> and then placing in JTextPane with HTML style.

All user tags and HTML stores in HashMap and parsing like this:

public static String parseBBToHTML(String text) {
    String html = text;

    Map<String,String> bbMap = new HashMap<String , String>();

    bbMap.put("\\[b\\]", "<strong>");
    bbMap.put("\\[/b\\]", "</strong>");
    ...
    bbMap.put("\\[color=(.+?)\\]", "<span style='color:$1;'>");
    bbMap.put("\\[/color\\]", "</span>");

    for (Map.Entry entry: bbMap.entrySet()) {
        html = html.replaceAll(entry.getKey().toString(), entry.getValue().toString());
    }

    return html;
}

And then returned value used for .setText() in JTextPane with animation effect with other method, what just type one letter after other with pause between them.

All works good, but now I'm thinking about one thing:

I'm imitating pause in typing animation with empty HTML-tag like this: <!!!!!!!!!!>

So 10 "!" with pause in 20ms between them give me 200ms of pause. I'm forming that "pause tags" with this method (it argument was int, but you'll see, why I use String now):

public static String pause(String ms){
    String pausedText = "";
    int time = Integer.parseInt(ms);
    for (int i = 0; i < (time/animationSpeed); i++) {
        pausedText = pausedText + "!";
    }       
    return "<"+pausedText+">";
}

I want use tag like [!2000] for pause to 2000ms. So I just put in my parser string like this:

bbMap.put("\\[\\!(.+?)\\]", Dialogue.pause("$1"));

...and it didn't working. It's giving in method Dialogue.pause literally string "$1", not $1 as value of parsing.

How can I use $1 as argument to form "pause tag" and them place it in text?

halfer
  • 19,824
  • 17
  • 99
  • 186
Lemis
  • 15
  • 6
  • Whats this class : Dialogue.pause –  Sep 21 '16 at 13:23
  • What does `entry.getValue().toString()` return in the pause case? – Thomas Sep 21 '16 at 13:23
  • Dialogue.pause is in my post. **public static String pause(String ms)** and so on. – Lemis Sep 21 '16 at 13:24
  • `entry.getValue().toString()` isn't returning, because program down with this: Exception in thread "main" java.lang.NumberFormatException: For input string: "$1" at java.lang.NumberFormatException.forInputString(Unknown Source) at java.lang.Integer.parseInt(Unknown Source) at java.lang.Integer.parseInt(Unknown Source) at vengine.Dialogue.pause(Dialogue.java:81) Sorry, can't format this. Strange. – Lemis Sep 21 '16 at 13:26
  • This reminds me of `Matcher#appendReplacement`. – Wiktor Stribiżew Sep 21 '16 at 13:27

3 Answers3

1

A String is just a single, static value. What you want is a String which is dynamically computed from another String.

You need to change your Map from this:

Map<String, String> bbMap = new HashMap<String, String>();

to this:

Map<String, UnaryOperator<String>> bbMap = new HashMap<>();

A UnaryOperator<String> is a function that takes a String argument and returns a String value. So, you would populate your Map like this:

bbMap.put("\\[b\\]", s -> "<strong>");
bbMap.put("\\[/b\\]", s -> "</strong>");
//...
bbMap.put("\\[color=(.+?)\\]", s -> "<span style='color:" + s + ";'>");
bbMap.put("\\[/color\\]", s -> "</span>");

bbMap.put("\\[\\!(.+?)\\]", s -> Dialogue.pause(s));

for (Map.Entry entry : bbMap.entrySet()) {
    StringBuffer buffer = new StringBuffer(html.length());

    Matcher matcher =
        Pattern.compile(entry.getKey().toString()).matcher(html);
    while (matcher.find()) {
        String match =
            (matcher.groupCount() > 0 ? matcher.group(1) : null);
        String replacement = entry.getValue().apply(match);
        matcher.appendReplacement(buffer,
            Matcher.quoteReplacement(replacement));
    }
    matcher.appendTail(buffer);

    html = buffer.toString();
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Wow, that's awesome! Never heard about Map with UnaryOperator! But it's not compile. `apply(String) is undefined for the type Object` I'm cast it with `String replacement = ((Function) entry.getValue()).apply(match);`, but I don't think, what it's a good thing. – Lemis Sep 21 '16 at 13:48
0

In Java $1 does not have a special meaning outside the capturing command, it is not a global variable. You'll need to parse the string and capture the group into a local variable to use it in the future.

Have a look here: Java Regex Replace with Capturing Group

This means your current approach with bbMap wont' work for these replacements.

You could do this: run your bbMap modifications first. Then run a second set of modifications bbMap2 where you first capture the group, count the length, create the string you want based on the length, then replace.

Community
  • 1
  • 1
Hugo Zaragoza
  • 574
  • 8
  • 25
0

If I'm not mistaken by calling bbMap.put("\\[\\!(.+?)\\]", Dialogue.pause("$1")); with your other code you want to achieve something like this:

  • the regex finds a match for group 1
  • Dialogue.pause("$1") is called with the content of group 1 (which should be a number)

The problem is that it doesn't work that way so you might consider refactoring your code. Instead of iterating over possible tags (i.e. the entries in bbMap) and looking for them in the input you might want to iterate over the input, get opening and closing tags and do a lookup in the map.

You can do that with regex but please note that this suffers from the same problem as HTML: it doesn't fit regex unless you really know how the data is structured etc.

So what you might want to do is use an expression to find all the tags, get the tag name/content and then do the replacement. It might then look like this:

Pattern tagPattern = Pattern.compile("\\[(/?)([^\\]]*)\\]");
Matcher tagMatcher = tagPattern.matcher( input );    

StringBuffer output = new StringBuffer();
while( tagMatcher.find() ) {
  String closeMarker = tagMatcher.group( 1 );
  String tagContent = tagMatcher.group( 2 );

  //use the tagContent to do the lookup in the map and create the replacement string
  String replacement = ...;

  tagMatcher.appendReplacement( output, replacement );
}
tagMatcher.appendTail( output ); 
Thomas
  • 87,414
  • 12
  • 119
  • 157
  • I'm forming input text with my own and only once, so there will be no error like `[!@#$arrrawet]` or something. Yes, maybe code structure like `get user text -> find tags -> replace them with HTML tags` will be better, than just put it in my parseBBToHTML and hope for the best. I'll try that in minute. – Lemis Sep 21 '16 at 13:36