Problem
I have a big string resource (basically the “about” text of the application) which contains styles (such as <b>
, <a>
etc.). The string resource is written on multiple lines, like this:
<string name="about_text"><big><b>About</b></big>n
Lorem ipsum dolor sit amet and all that stuff...n
n
More stuff.</string>
Now, just like in HTML, Android treats actual new lines (not the special n
) as spaces, so the text ends up looking something like this:
About
Lorem ipsum dolor sit
amet and all that
stuff...
More stuff.
Which looks pretty stupid. Now, I have two options:
- Write the whole thing on one line. I did not go with this because:
- It would be a pretty big line.
- The text needs to be translated, and putting it all on one line would give the translators a great headache.
- Remove the unneeded whitespace programatically.
I went with the second solution, but I’m not sure my implementation is optimal:
CharSequence aboutText = getText(R.string.about_text);
SpannableStringBuilder ssb = new SpannableStringBuilder(aboutText);
for (int i=0; i < ssb.length()-1; i++) {
if (ssb.charAt(i) == 'n' && ssb.charAt(i+1) == ' ') {
ssb.replace(i+1, i+2, "");
}
}
this.aboutText = (TextView) findViewById(R.id.about_text);
this.aboutText.setText(ssb);
It seems very hackish, but I could not find a better way. Is there a better way?
Solution
The answer is to put the newlines at the start of each line, directly before the text.
<string name="about_text"><big><b>About</b></big>
nLorem ipsum dolor sit amet and all that stuff...
nnMore stuff.
</string>
Therefore all the redundant whitespace appears at the end of each line (e.g. after “About”) where it doesn’t affect the appearance of your text.
Since this is XML, you can use the method of ignoring whitespace using comments:
<string name="about_text"><big><b>About</b></big>n<!--
-->Lorem ipsum dolor sit amet and all that stuff...n<!--
-->n<!--
-->More stuff.</string>
It may be ugly, but this technique has the advantage that the actual text content of your XML is the exact string you wanted it to be; there are no workarounds in code and you are not depending on any quirks.
This should work:
String aboutText = getText(R.string.about_text).toString();
aboutText = aboutText.replace("n ", "n");
toString()
is there to convert to a String
object.
I’m not sure string.xml
is such a good choice or long strings.
For the about dialog, I chose to have HTML page in assets
and use a WebView (use strings.xml
to store the name of the html file for the locale)
This is more flexible and feature rich.
I think your use of SpannableStringBuilder makes sense. You could adopt Character.isSpaceChar() or Character.isWhitespace() if you want to easily trim additional whitespace characters.
I realize it doesn’t directly address your question, but I wrote a similar function to remove trailing whitespace from a CharSequence this afternoon:
public static CharSequence trimTrailingWhitespace(CharSequence source) {
if(source == null)
return "";
int i = source.length();
// loop back to the first non-whitespace character
while(--i >= 0 && Character.isWhitespace(source.charAt(i))) {
}
return source.subSequence(0, i+1);
}
Edit: I ran into a similar problem this morning where I needed to remove internal blank lines, so I took a stab at writing a method that:
- minimizes the number of calls to replace(), and
- handles other forms of whitespace such as linefeeds, tabs, etc.
Again, this doesn’t exactly solve your problem (and as the other answers have pointed out, there are ways to work around your issue in the source), but an approach similar to this would be suitable for strings you didn’t author, e.g. downloaded strings.
public static CharSequence removeExcessBlankLines(CharSequence source) {
if(source == null)
return "";
int newlineStart = -1;
int nbspStart = -1;
int consecutiveNewlines = 0;
SpannableStringBuilder ssb = new SpannableStringBuilder(source);
for(int i = 0; i < ssb.length(); ++i) {
final char c = ssb.charAt(i);
if(c == 'n') {
if(consecutiveNewlines == 0)
newlineStart = i;
++consecutiveNewlines;
nbspStart = -1;
}
else if(c == 'u00A0') {
if(nbspStart == -1)
nbspStart = i;
}
else if(consecutiveNewlines > 0) {
// note: also removes lines containing only whitespace,
// or nbsp; except at the beginning of a line
if( !Character.isWhitespace(c) && c != 'u00A0') {
// we've reached the end
if(consecutiveNewlines > 2) {
// replace the many with the two
ssb.replace(newlineStart, nbspStart > newlineStart ? nbspStart : i, "nn");
i -= i - newlineStart;
}
consecutiveNewlines = 0;
nbspStart = -1;
}
}
}
return ssb;
}