3

I'm trying to make a string formatting mechanism, which pretty much looks like the Winamp Advanced Title Formatting.

I have some 'variables' (or metadata fields) bound to object properties, in the form %varname%. So, for example, the %title% metadata field is bound to a song title, say 'Conquest of Paradise', the %artist% metadata field is bound to the song artist, say 'Vangelis', and the %feat% metadata field is bound to the featuring artists, say 'English Chamber Choir'.

Now I want to display the song depending on a given formatting, for example:

%title%[ (by %artist%[ featuring %feat%])]

Square brackets mean don't display unless (all) metadata inside the brackets were set. Nesting of square brackets should be possible.
So abovementioned formatting string says: display the metadata field %title% and, if %artist% is set (not an empty string), display (by %artist%), but if the %feat% metadata field is also non-empty, then display that field also. In the abovementioned example, it would become:

Conquest of Paradise (by Vangelis featuring English Chamber Choir)

Now how do I make such mechanism? Where do I start?

I guess that I have to tokenize the string and then per 'section' search for metadata tags?

1
  • So what you are really looking for is tokenizing, not formatting? For formatting read the Javadoc for String.format(). Commented Apr 5, 2013 at 6:59

2 Answers 2

3

I would build up a tree structure that represents the pattern. For your example, it would look like:

root
 + variable (title)
 + group 
   + text (" (by ")
   + variable (artist)
   + group
     + text (" featuring ")
     + variable (feat)
   + text (")")

Then when you evaluate meta data against your tree, you store at the group level whether all variables and sub-groups in the group evaluated, and if so use the text.

Your tree classes would look something like:

interface Node { String evaluate(Map<String, String> metaData); }

class Group implements Node 
{
  private final List<Node> _children;

  Group(final List<Node> children) { _children = children; }

  @Override
  public String evaluate(final Map<String, String> metaData)
  {
    final StringBuilder sb = new StringBuilder(); 
    for (final Node node : _children)
    {
      final String subText = node.evaluate(metaData);
      if (subText == null)
        return null;
      sb.append(subText);
    }
    return sb.toString();
  }
}

class Text implements Node 
{
  private final String _text;

  Text(final String text) { _text = text; }

  @Override
  public String evaluate(final Map<String, String> metaData)
  {
    return _text;
  }
}

class Variable implements Node 
{
  private final String _variable;

  Variable(final String variable) { _variable = variable; }

  @Override
  public String evaluate(final Map<String, String> metaData)
  {
    return metaData.get(_variable);
  }
}

All that's left to do is to work out how to parse your string to create the tree structure.

Sign up to request clarification or add additional context in comments.

Comments

1

Based on the suggestion of SimonC, I've written a tokenizer what executes what has been suggested, to split the formatting string into tokens.

public class Main {

    private static void buildTree(String format) {
        Stack<Token> st = new Stack<>();
        StringBuilder sb = new StringBuilder();
        GroupToken root = new GroupToken();
        st.push(root);

        boolean var = false;

        for (int i = 0; i < format.length(); i++) {
            char currentChar = format.charAt(i);
            switch (currentChar) {
                case '[':
                    String str = sb.toString();
                    sb.setLength(0); // Flush the StringBuilder
                    if (!str.equals("")) {
                        ((GroupToken) st.peek()).add(new TextToken(str));
                    }
                    GroupToken gt = new GroupToken();
                    ((GroupToken) st.peek()).add(gt);
                    st.push(gt);
                    break;
                case ']':
                    str = sb.toString();
                    sb.setLength(0); // Flush the StringBuilder
                    if (!str.equals("")) {
                        ((GroupToken) st.peek()).add(new TextToken(str));
                    }
                    st.pop();
                    break;
                case '%':
                    var = !var;
                    if (var) {
                        str = sb.toString();
                        sb.setLength(0); // Flush the StringBuilder
                        if (!str.equals("")) {
                            ((GroupToken) st.peek()).add(new TextToken(str));
                        }
                    }
                    else {
                        str = sb.toString();
                        sb.setLength(0); // Flush the StringBuilder
                        ((GroupToken) st.peek()).add(new VariableToken(str));
                    }
                    break;
                default:
                    sb.append(currentChar);
                    break;
            }
        }
        // Process the last remains of the string buffer...
        String str = sb.toString();
        sb.setLength(0); // Flush the StringBuilder
        if (!str.equals("")) {
            ((GroupToken) st.peek()).add(new TextToken(str));
        }
        st.pop();
        System.out.println(root);
    }

    public static void main(String[] arguments) throws Exception {
        buildTree("%title%[ (%alttitle%[, #%track%])]");
    }

}

abstract class Token {

    public abstract String toString(int indent);

}

class TextToken extends Token {

    private String text;

    public TextToken(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return toString(0);
    }

    @Override
    public String toString(int indent) {
        return "TextToken[\"" + this.text + "\"]\n";
    }
}

class VariableToken extends Token {

    private String text;

    public VariableToken(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return toString(0);
    }

    @Override
    public String toString(int indent) {
        return "VariableToken[\"" + this.text + "\"]\n";
    }
}

class GroupToken extends Token {

    ArrayList<Token> tokens = new ArrayList<>();

    public GroupToken() { }

    public void add(Token token) {
        this.tokens.add(token);
    }

    @Override
    public String toString() {
        return toString(0);
    }

    @Override
    public String toString(int indent) {
        String out = "GroupToken[\n";
        for (Token t : this.tokens) {
            out += StringUtils.pad("", 4 * (indent + 1), ' ') + t.toString(indent + 1);
        }
        out += StringUtils.pad("", 4 * indent, ' ') + "]\n";
        return out;
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.