Phrase是一个字符串格式化的开源项目,整个项目只有一个类Phrase.java,相比于String.format,通过phrase格式化字符串代码更具可读性。
源码:phrase项目的源代码很简单,里面总共只有一个类:Phrase.java,代码如下:
package com.eebbk.learnspell.util;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import android.app.Fragment;
import android.content.Context;
import android.content.res.Resources;
import android.text.SpannableStringBuilder;
import android.view.View;
public final class Phrase {
private final CharSequence pattern;
private final Set<String> keys = new HashSet<String>();
private final Map<String, CharSequence> keysToValues = new HashMap<String, CharSequence>();
private CharSequence formatted;
private Token head;
private char curChar;
private int curCharIndex;
private static final int EOF = 0;
public static Phrase from(Fragment f, int patternResourceId) {
return from(f.getResources(), patternResourceId);
}
public static Phrase from(View v, int patternResourceId) {
return from(v.getResources(), patternResourceId);
}
public static Phrase from(Context c, int patternResourceId) {
return from(c.getResources(), patternResourceId);
}
public static Phrase from(Resources r, int patternResourceId) {
return from(r.getText(patternResourceId));
}
public static Phrase from(CharSequence pattern) {
return new Phrase(pattern);
}
public Phrase put(String key, CharSequence value) {
if (!keys.contains(key)) {
throw new IllegalArgumentException("Invalid key: " + key);
}
if (value == null) {
throw new IllegalArgumentException("Null value for '" + key + "'");
}
keysToValues.put(key, value);
formatted = null;
return this;
}
public Phrase put(String key, int value) {
if (!keys.contains(key)) {
throw new IllegalArgumentException("Invalid key: " + key);
}
keysToValues.put(key, Integer.toString(value));
formatted = null;
return this;
}
public Phrase putOptional(String key, CharSequence value) {
return keys.contains(key) ? put(key, value) : this;
}
public Phrase putOptional(String key, int value) {
return keys.contains(key) ? put(key, value) : this;
}
public CharSequence format() {
if (formatted == null) {
if (!keysToValues.keySet().containsAll(keys)) {
Set<String> missingKeys = new HashSet<String>(keys);
missingKeys.removeAll(keysToValues.keySet());
throw new IllegalArgumentException("Missing keys: " + missingKeys);
}
SpannableStringBuilder sb = new SpannableStringBuilder(pattern);
for (Token t = head; t != null; t = t.next) {
t.expand(sb, keysToValues);
}
formatted = sb;
}
return formatted;
}
@Override
public String toString() {
return pattern.toString();
}
private Phrase(CharSequence pattern) {
curChar = (pattern.length() > 0) ? pattern.charAt(0) : EOF;
this.pattern = pattern;
Token prev = null;
Token next;
while ((next = token(prev)) != null) {
if (head == null) head = next;
prev = next;
}
}
private Token token(Token prev) {
if (curChar == EOF) {
return null;
}
if (curChar == '{') {
char nextChar = lookahead();
if (nextChar == '{') {
return leftCurlyBracket(prev);
} else if (nextChar >= 'a' && nextChar <= 'z') {
return key(prev);
} else {
throw new IllegalArgumentException(
"Unexpected character '" + nextChar + "'; expected key.");
}
}
return text(prev);
}
private KeyToken key(Token prev) {
StringBuilder sb = new StringBuilder();
consume();
while ((curChar >= 'a' && curChar <= 'z') || curChar == '_') {
sb.append(curChar);
consume();
}
if (curChar != '}') {
throw new IllegalArgumentException("Missing closing brace: }");
}
consume();
if (sb.length() == 0) {
throw new IllegalArgumentException("Empty key: {}");
}
String key = sb.toString();
keys.add(key);
return new KeyToken(prev, key);
}
private TextToken text(Token prev) {
int startIndex = curCharIndex;
while (curChar != '{' && curChar != EOF) {
consume();
}
return new TextToken(prev, curCharIndex - startIndex);
}
private LeftCurlyBracketToken leftCurlyBracket(Token prev) {
consume();
consume();
return new LeftCurlyBracketToken(prev);
}
private char lookahead() {
return curCharIndex < pattern.length() - 1 ? pattern.charAt(curCharIndex + 1) : EOF;
}
private void consume() {
curCharIndex++;
curChar = (curCharIndex == pattern.length()) ? EOF : pattern.charAt(curCharIndex);
}
private abstract static class Token {
private final Token prev;
private Token next;
protected Token(Token prev) {
this.prev = prev;
if (prev != null) prev.next = this;
}
abstract void expand(SpannableStringBuilder target, Map<String, CharSequence> data);
abstract int getFormattedLength();
final int getFormattedStart() {
if (prev == null) {
return 0;
} else {
return prev.getFormattedStart() + prev.getFormattedLength();
}
}
}
private static class TextToken extends Token {
private final int textLength;
TextToken(Token prev, int textLength) {
super(prev);
this.textLength = textLength;
}
@Override void expand(SpannableStringBuilder target, Map<String, CharSequence> data) {
}
@Override int getFormattedLength() {
return textLength;
}
}
private static class LeftCurlyBracketToken extends Token {
LeftCurlyBracketToken(Token prev) {
super(prev);
}
@Override void expand(SpannableStringBuilder target, Map<String, CharSequence> data) {
int start = getFormattedStart();
target.replace(start, start + 2, "{");
}
@Override int getFormattedLength() {
return 1;
}
}
private static class KeyToken extends Token {
private final String key;
private CharSequence value;
KeyToken(Token prev, String key) {
super(prev);
this.key = key;
}
@Override void expand(SpannableStringBuilder target, Map<String, CharSequence> data) {
value = data.get(key);
int replaceFrom = getFormattedStart();
int replaceTo = replaceFrom + key.length() + 2;
target.replace(replaceFrom, replaceTo, value);
}
@Override int getFormattedLength() {
return value.length();
}
}
}
字符串格式化原理
通过阅读Phrase.java的代码可知,它用”{“和”}”将需要格式化的内容包起来,然后用键值对给需要改变的内容传值,包起来的内容为键,值为动态设置的内容,比如:
"Hi {first_name}, you are {age} years old."
我们要最终的显示内容为:“Hi UperOne, you are 26 years old.”这里的first_name和age是键,值为UperOne和26。
使用起来很简单,通过键值对的方式插值:
CharSequence parseStr = Phrase.from("Hi {first_name}, you are {age} years old.")
.put("first_name", "UperOne")
.put("age", "26")
.format();
mParseTxt.setText( parseStr );